Browse Source

Provide v1 and v2 infohashes in UI (#15097)

adaptive-webui-19844
Vladimir Golovnev 3 years ago committed by GitHub
parent
commit
37f227ae74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      src/app/application.cpp
  2. 18
      src/base/bittorrent/infohash.cpp
  3. 2
      src/base/bittorrent/infohash.h
  4. 6
      src/gui/addnewtorrentdialog.cpp
  5. 72
      src/gui/addnewtorrentdialog.ui
  6. 6
      src/gui/optionsdialog.cpp
  7. 9
      src/gui/properties/propertieswidget.cpp
  8. 74
      src/gui/properties/propertieswidget.ui
  9. 51
      src/gui/transferlistwidget.cpp
  10. 26
      src/gui/transferlistwidget.h
  11. 5
      src/webui/api/serialize/serialize_torrent.cpp
  12. 3
      src/webui/api/serialize/serialize_torrent.h
  13. 2
      src/webui/api/torrentscontroller.cpp
  14. 4
      src/webui/www/private/index.html
  15. 8
      src/webui/www/private/scripts/client.js
  16. 2
      src/webui/www/private/scripts/dynamicTable.js
  17. 34
      src/webui/www/private/scripts/mocha-init.js
  18. 4
      src/webui/www/private/scripts/prop-files.js
  19. 23
      src/webui/www/private/scripts/prop-general.js
  20. 6
      src/webui/www/private/scripts/prop-peers.js
  21. 2
      src/webui/www/private/scripts/prop-trackers.js
  22. 2
      src/webui/www/private/scripts/prop-webseeds.js
  23. 4
      src/webui/www/private/views/preferences.html
  24. 8
      src/webui/www/private/views/properties.html

6
src/app/application.cpp

@ -353,6 +353,12 @@ void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const
program.replace(i, 2, torrent->tags().join(QLatin1String(","))); program.replace(i, 2, torrent->tags().join(QLatin1String(",")));
break; break;
case u'I': case u'I':
program.replace(i, 2, (torrent->infoHash().v1().isValid() ? torrent->infoHash().v1().toString() : QLatin1String("-")));
break;
case u'J':
program.replace(i, 2, (torrent->infoHash().v2().isValid() ? torrent->infoHash().v2().toString() : QLatin1String("-")));
break;
case u'K':
program.replace(i, 2, torrent->id().toString()); program.replace(i, 2, torrent->id().toString());
break; break;
case u'L': case u'L':

18
src/base/bittorrent/infohash.cpp

@ -41,6 +41,24 @@ bool BitTorrent::InfoHash::isValid() const
return m_valid; return m_valid;
} }
SHA1Hash BitTorrent::InfoHash::v1() const
{
#if (LIBTORRENT_VERSION_NUM >= 20000)
return (m_nativeHash.has_v1() ? SHA1Hash(m_nativeHash.v1) : SHA1Hash());
#else
return {m_nativeHash};
#endif
}
SHA256Hash BitTorrent::InfoHash::v2() const
{
#if (LIBTORRENT_VERSION_NUM >= 20000)
return (m_nativeHash.has_v2() ? SHA256Hash(m_nativeHash.v2) : SHA256Hash());
#else
return {};
#endif
}
BitTorrent::TorrentID BitTorrent::InfoHash::toTorrentID() const BitTorrent::TorrentID BitTorrent::InfoHash::toTorrentID() const
{ {
#if (LIBTORRENT_VERSION_NUM >= 20000) #if (LIBTORRENT_VERSION_NUM >= 20000)

2
src/base/bittorrent/infohash.h

@ -69,6 +69,8 @@ namespace BitTorrent
InfoHash(const WrappedType &nativeHash); InfoHash(const WrappedType &nativeHash);
bool isValid() const; bool isValid() const;
SHA1Hash v1() const;
SHA256Hash v2() const;
TorrentID toTorrentID() const; TorrentID toTorrentID() const;
operator WrappedType() const; operator WrappedType() const;

6
src/gui/addnewtorrentdialog.cpp

@ -296,7 +296,8 @@ bool AddNewTorrentDialog::loadTorrentImpl()
return false; return false;
} }
m_ui->labelHashData->setText(torrentID.toString()); m_ui->labelInfohash1Data->setText(m_torrentInfo.infoHash().v1().isValid() ? m_torrentInfo.infoHash().v1().toString() : tr("N/A"));
m_ui->labelInfohash2Data->setText(m_torrentInfo.infoHash().v2().isValid() ? m_torrentInfo.infoHash().v2().toString() : tr("N/A"));
setupTreeview(); setupTreeview();
TMMChanged(m_ui->comboTTM->currentIndex()); TMMChanged(m_ui->comboTTM->currentIndex());
return true; return true;
@ -348,7 +349,8 @@ bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri)
BitTorrent::Session::instance()->downloadMetadata(magnetUri); BitTorrent::Session::instance()->downloadMetadata(magnetUri);
setMetadataProgressIndicator(true, tr("Retrieving metadata...")); setMetadataProgressIndicator(true, tr("Retrieving metadata..."));
m_ui->labelHashData->setText(torrentID.toString()); m_ui->labelInfohash1Data->setText(magnetUri.infoHash().v1().isValid() ? magnetUri.infoHash().v1().toString() : tr("N/A"));
m_ui->labelInfohash2Data->setText(magnetUri.infoHash().v2().isValid() ? magnetUri.infoHash().v2().toString() : tr("N/A"));
m_magnetURI = magnetUri; m_magnetURI = magnetUri;
return true; return true;

72
src/gui/addnewtorrentdialog.ui

@ -249,10 +249,10 @@
<string>Torrent information</string> <string>Torrent information</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0"> <item row="2" column="0">
<widget class="QLabel" name="labelDate"> <widget class="QLabel" name="labelInfohash1">
<property name="text"> <property name="text">
<string>Date:</string> <string>Info hash v1:</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -262,24 +262,7 @@
<item row="1" column="1"> <item row="1" column="1">
<widget class="QLabel" name="labelDateData"/> <widget class="QLabel" name="labelDateData"/>
</item> </item>
<item row="0" column="0"> <item row="4" column="1">
<widget class="QLabel" name="labelSize">
<property name="text">
<string>Size:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="labelHashData">
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QScrollArea" name="scrollArea"> <widget class="QScrollArea" name="scrollArea">
<property name="styleSheet"> <property name="styleSheet">
<string notr="true">background-color: rgba(0, 0, 0, 0);</string> <string notr="true">background-color: rgba(0, 0, 0, 0);</string>
@ -295,7 +278,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>333</width> <width>317</width>
<height>69</height> <height>69</height>
</rect> </rect>
</property> </property>
@ -335,20 +318,51 @@
</widget> </widget>
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="4" column="0">
<widget class="QLabel" name="labelHash"> <widget class="QLabel" name="labelComment">
<property name="text"> <property name="text">
<string>Hash:</string> <string>Comment:</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="labelSize">
<property name="text">
<string>Size:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="labelInfohash1Data">
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelDate">
<property name="text">
<string>Date:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0"> <item row="3" column="0">
<widget class="QLabel" name="labelComment"> <widget class="QLabel" name="labelInfohash2">
<property name="text"> <property name="text">
<string>Comment:</string> <string>Info hash v2</string>
</property> </property>
<property name="alignment"> </widget>
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> </item>
<item row="3" column="1">
<widget class="QLabel" name="labelInfohash2Data">
<property name="text">
<string/>
</property> </property>
</widget> </widget>
</item> </item>

6
src/gui/optionsdialog.cpp

@ -388,7 +388,7 @@ OptionsDialog::OptionsDialog(QWidget *parent)
connect(m_ui->lineEditAutoRun, &QLineEdit::textChanged, this, &ThisType::enableApplyButton); connect(m_ui->lineEditAutoRun, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
connect(m_ui->autoRunConsole, &QCheckBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->autoRunConsole, &QCheckBox::toggled, this, &ThisType::enableApplyButton);
const QString autoRunStr = QString("%1\n %2\n %3\n %4\n %5\n %6\n %7\n %8\n %9\n %10\n %11\n%12") const QString autoRunStr = QString("%1\n %2\n %3\n %4\n %5\n %6\n %7\n %8\n %9\n %10\n %11\n %12\n %13\n%14")
.arg(tr("Supported parameters (case sensitive):") .arg(tr("Supported parameters (case sensitive):")
, tr("%N: Torrent name") , tr("%N: Torrent name")
, tr("%L: Category") , tr("%L: Category")
@ -399,7 +399,9 @@ OptionsDialog::OptionsDialog(QWidget *parent)
, tr("%C: Number of files") , tr("%C: Number of files")
, tr("%Z: Torrent size (bytes)")) , tr("%Z: Torrent size (bytes)"))
.arg(tr("%T: Current tracker") .arg(tr("%T: Current tracker")
, tr("%I: Info hash") , tr("%I: Info hash v1 (or '-' if unavailable)")
, tr("%J: Info hash v2 (or '-' if unavailable)")
, tr("%K: Torrent ID (either sha-1 info hash for v1 torrent or truncated sha-256 info hash for v2/hybrid torrent)")
, tr("Tip: Encapsulate parameter with quotation marks to avoid text being cut off at whitespace (e.g., \"%N\")")); , tr("Tip: Encapsulate parameter with quotation marks to avoid text being cut off at whitespace (e.g., \"%N\")"));
m_ui->labelAutoRunParam->setText(autoRunStr); m_ui->labelAutoRunParam->setText(autoRunStr);

9
src/gui/properties/propertieswidget.cpp

@ -232,7 +232,8 @@ void PropertiesWidget::clear()
m_ui->labelSavePathVal->clear(); m_ui->labelSavePathVal->clear();
m_ui->labelCreatedOnVal->clear(); m_ui->labelCreatedOnVal->clear();
m_ui->labelTotalPiecesVal->clear(); m_ui->labelTotalPiecesVal->clear();
m_ui->labelHashVal->clear(); m_ui->labelInfohash1Val->clear();
m_ui->labelInfohash2Val->clear();
m_ui->labelCommentVal->clear(); m_ui->labelCommentVal->clear();
m_ui->labelProgressVal->clear(); m_ui->labelProgressVal->clear();
m_ui->labelAverageAvailabilityVal->clear(); m_ui->labelAverageAvailabilityVal->clear();
@ -312,9 +313,9 @@ void PropertiesWidget::loadTorrentInfos(BitTorrent::Torrent *const torrent)
// Save path // Save path
updateSavePath(m_torrent); updateSavePath(m_torrent);
// Info hash (Truncated info hash (torrent ID) with libtorrent2) // Info hashes
// TODO: Update label for this property to express its meaning more clearly (or change it to display real info hash(es)) m_ui->labelInfohash1Val->setText(m_torrent->infoHash().v1().isValid() ? m_torrent->infoHash().v1().toString() : tr("N/A"));
m_ui->labelHashVal->setText(m_torrent->id().toString()); m_ui->labelInfohash2Val->setText(m_torrent->infoHash().v2().isValid() ? m_torrent->infoHash().v2().toString() : tr("N/A"));
m_propListModel->model()->clear(); m_propListModel->model()->clear();
if (m_torrent->hasMetadata()) if (m_torrent->hasMetadata())
{ {

74
src/gui/properties/propertieswidget.ui

@ -11,6 +11,9 @@
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin"> <property name="leftMargin">
<number>0</number> <number>0</number>
</property> </property>
@ -23,9 +26,6 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<property name="spacing">
<number>0</number>
</property>
<item> <item>
<widget class="QStackedWidget" name="stackedProperties"> <widget class="QStackedWidget" name="stackedProperties">
<property name="currentIndex"> <property name="currentIndex">
@ -788,7 +788,7 @@
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel" name="labelHash"> <widget class="QLabel" name="labelInfohash1">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred"> <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch> <horstretch>0</horstretch>
@ -796,15 +796,31 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>Torrent Hash:</string> <string>Info Hash v1:</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1" colspan="5"> <item row="3" column="0">
<widget class="QLabel" name="labelHashVal"> <widget class="QLabel" name="labelInfohash2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Info Hash v2:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="1" colspan="5">
<widget class="QLabel" name="labelInfohash2Val">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred"> <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch> <horstretch>0</horstretch>
@ -819,7 +835,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0"> <item row="4" column="0">
<widget class="QLabel" name="labelSavePath"> <widget class="QLabel" name="labelSavePath">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred"> <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
@ -835,8 +851,24 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1" colspan="5"> <item row="5" column="0">
<widget class="QLabel" name="labelSavePathVal"> <widget class="QLabel" name="labelComment">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Comment:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
</property>
</widget>
</item>
<item row="2" column="1" colspan="5">
<widget class="QLabel" name="labelInfohash1Val">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred"> <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch> <horstretch>0</horstretch>
@ -846,31 +878,31 @@
<property name="textFormat"> <property name="textFormat">
<enum>Qt::PlainText</enum> <enum>Qt::PlainText</enum>
</property> </property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags"> <property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set> <set>Qt::TextSelectableByMouse</set>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0"> <item row="4" column="1" colspan="5">
<widget class="QLabel" name="labelComment"> <widget class="QLabel" name="labelSavePathVal">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred"> <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="textFormat">
<string>Comment:</string> <enum>Qt::PlainText</enum>
</property> </property>
<property name="alignment"> <property name="wordWrap">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set> <bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1" colspan="5"> <item row="5" column="1" colspan="5">
<widget class="QLabel" name="labelCommentVal"> <widget class="QLabel" name="labelCommentVal">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding">

51
src/gui/transferlistwidget.cpp

@ -487,7 +487,33 @@ void TransferListWidget::copySelectedNames() const
qApp->clipboard()->setText(torrentNames.join('\n')); qApp->clipboard()->setText(torrentNames.join('\n'));
} }
void TransferListWidget::copySelectedHashes() const void TransferListWidget::copySelectedInfohashes(const CopyInfohashPolicy policy) const
{
const auto selectedTorrents = getSelectedTorrents();
QStringList infoHashes;
infoHashes.reserve(selectedTorrents.size());
switch (policy)
{
case CopyInfohashPolicy::Version1:
for (const BitTorrent::Torrent *torrent : selectedTorrents)
{
if (const auto infoHash = torrent->infoHash().v1(); infoHash.isValid())
infoHashes << infoHash.toString();
}
break;
case CopyInfohashPolicy::Version2:
for (const BitTorrent::Torrent *torrent : selectedTorrents)
{
if (const auto infoHash = torrent->infoHash().v2(); infoHash.isValid())
infoHashes << infoHash.toString();
}
break;
}
qApp->clipboard()->setText(infoHashes.join('\n'));
}
void TransferListWidget::copySelectedIDs() const
{ {
QStringList torrentIDs; QStringList torrentIDs;
for (BitTorrent::Torrent *const torrent : asConst(getSelectedTorrents())) for (BitTorrent::Torrent *const torrent : asConst(getSelectedTorrents()))
@ -827,10 +853,14 @@ void TransferListWidget::displayListMenu(const QPoint &)
connect(actionForceReannounce, &QAction::triggered, this, &TransferListWidget::reannounceSelectedTorrents); connect(actionForceReannounce, &QAction::triggered, this, &TransferListWidget::reannounceSelectedTorrents);
auto *actionCopyMagnetLink = new QAction(UIThemeManager::instance()->getIcon("kt-magnet"), tr("Magnet link"), listMenu); auto *actionCopyMagnetLink = new QAction(UIThemeManager::instance()->getIcon("kt-magnet"), tr("Magnet link"), listMenu);
connect(actionCopyMagnetLink, &QAction::triggered, this, &TransferListWidget::copySelectedMagnetURIs); connect(actionCopyMagnetLink, &QAction::triggered, this, &TransferListWidget::copySelectedMagnetURIs);
auto *actionCopyID = new QAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Torrent ID"), listMenu);
connect(actionCopyID, &QAction::triggered, this, &TransferListWidget::copySelectedIDs);
auto *actionCopyName = new QAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Name"), listMenu); auto *actionCopyName = new QAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Name"), listMenu);
connect(actionCopyName, &QAction::triggered, this, &TransferListWidget::copySelectedNames); connect(actionCopyName, &QAction::triggered, this, &TransferListWidget::copySelectedNames);
auto *actionCopyHash = new QAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Hash"), listMenu); auto *actionCopyHash1 = new QAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Info hash v1"), listMenu);
connect(actionCopyHash, &QAction::triggered, this, &TransferListWidget::copySelectedHashes); connect(actionCopyHash1, &QAction::triggered, this, [this]() { copySelectedInfohashes(CopyInfohashPolicy::Version1); });
auto *actionCopyHash2 = new QAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Info hash v2"), listMenu);
connect(actionCopyHash2, &QAction::triggered, this, [this]() { copySelectedInfohashes(CopyInfohashPolicy::Version2); });
auto *actionSuperSeedingMode = new TriStateAction(tr("Super seeding mode"), listMenu); auto *actionSuperSeedingMode = new TriStateAction(tr("Super seeding mode"), listMenu);
connect(actionSuperSeedingMode, &QAction::triggered, this, &TransferListWidget::setSelectedTorrentsSuperSeeding); connect(actionSuperSeedingMode, &QAction::triggered, this, &TransferListWidget::setSelectedTorrentsSuperSeeding);
auto *actionRename = new QAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename..."), listMenu); auto *actionRename = new QAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename..."), listMenu);
@ -860,6 +890,7 @@ void TransferListWidget::displayListMenu(const QPoint &)
bool first = true; bool first = true;
TagSet tagsInAny; TagSet tagsInAny;
TagSet tagsInAll; TagSet tagsInAll;
bool hasInfohashV1 = false, hasInfohashV2 = false;
for (const QModelIndex &index : selectedIndexes) for (const QModelIndex &index : selectedIndexes)
{ {
@ -939,11 +970,17 @@ void TransferListWidget::displayListMenu(const QPoint &)
if (torrent->hasMetadata()) if (torrent->hasMetadata())
needsPreview = true; needsPreview = true;
if (!hasInfohashV1 && torrent->infoHash().v1().isValid())
hasInfohashV1 = true;
if (!hasInfohashV2 && torrent->infoHash().v2().isValid())
hasInfohashV2 = true;
first = false; first = false;
if (oneHasMetadata && oneNotSeed && !allSameSequentialDownloadMode if (oneHasMetadata && oneNotSeed && !allSameSequentialDownloadMode
&& !allSamePrioFirstlast && !allSameSuperSeeding && !allSameCategory && !allSamePrioFirstlast && !allSameSuperSeeding && !allSameCategory
&& needsStart && needsForce && needsPause && needsPreview && !allSameAutoTMM) && needsStart && needsForce && needsPause && needsPreview && !allSameAutoTMM
&& hasInfohashV1 && hasInfohashV2)
{ {
break; break;
} }
@ -1084,8 +1121,12 @@ void TransferListWidget::displayListMenu(const QPoint &)
QMenu *copySubMenu = listMenu->addMenu( QMenu *copySubMenu = listMenu->addMenu(
UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy")); UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy"));
copySubMenu->addAction(actionCopyName); copySubMenu->addAction(actionCopyName);
copySubMenu->addAction(actionCopyHash); copySubMenu->addAction(actionCopyHash1);
actionCopyHash1->setEnabled(hasInfohashV1);
copySubMenu->addAction(actionCopyHash2);
actionCopyHash2->setEnabled(hasInfohashV2);
copySubMenu->addAction(actionCopyMagnetLink); copySubMenu->addAction(actionCopyMagnetLink);
copySubMenu->addAction(actionCopyID);
listMenu->popup(QCursor::pos()); listMenu->popup(QCursor::pos());
} }

26
src/gui/transferlistwidget.h

@ -42,9 +42,16 @@ namespace BitTorrent
class TorrentID; class TorrentID;
} }
enum class CopyInfohashPolicy
{
Version1,
Version2
};
class TransferListWidget final : public QTreeView class TransferListWidget final : public QTreeView
{ {
Q_OBJECT Q_OBJECT
Q_DISABLE_COPY_MOVE(TransferListWidget)
public: public:
TransferListWidget(QWidget *parent, MainWindow *mainWindow); TransferListWidget(QWidget *parent, MainWindow *mainWindow);
@ -74,7 +81,8 @@ public slots:
void bottomQueuePosSelectedTorrents(); void bottomQueuePosSelectedTorrents();
void copySelectedMagnetURIs() const; void copySelectedMagnetURIs() const;
void copySelectedNames() const; void copySelectedNames() const;
void copySelectedHashes() const; void copySelectedInfohashes(CopyInfohashPolicy policy) const;
void copySelectedIDs() const;
void openSelectedTorrentsFolder() const; void openSelectedTorrentsFolder() const;
void recheckSelectedTorrents(); void recheckSelectedTorrents();
void reannounceSelectedTorrents(); void reannounceSelectedTorrents();
@ -91,13 +99,10 @@ public slots:
void previewFile(const QString &filePath); void previewFile(const QString &filePath);
void renameSelectedTorrent(); void renameSelectedTorrent();
protected: signals:
QModelIndex mapToSource(const QModelIndex &index) const; void currentTorrentChanged(BitTorrent::Torrent *const torrent);
QModelIndex mapFromSource(const QModelIndex &index) const;
bool loadSettings();
QVector<BitTorrent::Torrent *> getSelectedTorrents() const;
protected slots: private slots:
void torrentDoubleClicked(); void torrentDoubleClicked();
void displayListMenu(const QPoint &); void displayListMenu(const QPoint &);
void currentChanged(const QModelIndex &current, const QModelIndex&) override; void currentChanged(const QModelIndex &current, const QModelIndex&) override;
@ -108,11 +113,12 @@ protected slots:
void askNewCategoryForSelection(); void askNewCategoryForSelection();
void saveSettings(); void saveSettings();
signals:
void currentTorrentChanged(BitTorrent::Torrent *const torrent);
private: private:
void wheelEvent(QWheelEvent *event) override; void wheelEvent(QWheelEvent *event) override;
QModelIndex mapToSource(const QModelIndex &index) const;
QModelIndex mapFromSource(const QModelIndex &index) const;
bool loadSettings();
QVector<BitTorrent::Torrent *> getSelectedTorrents() const;
void askAddTagsForSelection(); void askAddTagsForSelection();
void editTorrentTrackers(); void editTorrentTrackers();
void confirmRemoveAllTagsForSelection(); void confirmRemoveAllTagsForSelection();

5
src/webui/api/serialize/serialize_torrent.cpp

@ -96,8 +96,9 @@ QVariantMap serialize(const BitTorrent::Torrent &torrent)
}; };
return { return {
// TODO: Add fields for real SHA1 and SHA256 hashes {KEY_TORRENT_ID, torrent.id().toString()},
{KEY_TORRENT_ID, QString(torrent.id().toString())}, {KEY_TORRENT_INFOHASHV1, torrent.infoHash().v1().toString()},
{KEY_TORRENT_INFOHASHV2, torrent.infoHash().v2().toString()},
{KEY_TORRENT_NAME, torrent.name()}, {KEY_TORRENT_NAME, torrent.name()},
{KEY_TORRENT_MAGNET_URI, torrent.createMagnetURI()}, {KEY_TORRENT_MAGNET_URI, torrent.createMagnetURI()},
{KEY_TORRENT_SIZE, torrent.wantedSize()}, {KEY_TORRENT_SIZE, torrent.wantedSize()},

3
src/webui/api/serialize/serialize_torrent.h

@ -36,7 +36,10 @@ namespace BitTorrent
} }
// Torrent keys // Torrent keys
// TODO: Rename it to `id`.
inline const char KEY_TORRENT_ID[] = "hash"; inline const char KEY_TORRENT_ID[] = "hash";
inline const char KEY_TORRENT_INFOHASHV1[] = "infohash_v1";
inline const char KEY_TORRENT_INFOHASHV2[] = "infohash_v2";
inline const char KEY_TORRENT_NAME[] = "name"; inline const char KEY_TORRENT_NAME[] = "name";
inline const char KEY_TORRENT_MAGNET_URI[] = "magnet_uri"; inline const char KEY_TORRENT_MAGNET_URI[] = "magnet_uri";
inline const char KEY_TORRENT_SIZE[] = "size"; inline const char KEY_TORRENT_SIZE[] = "size";

2
src/webui/api/torrentscontroller.cpp

@ -382,6 +382,8 @@ void TorrentsController::propertiesAction()
QJsonObject dataDict; QJsonObject dataDict;
dataDict[KEY_TORRENT_INFOHASHV1] = torrent->infoHash().v1().toString();
dataDict[KEY_TORRENT_INFOHASHV2] = torrent->infoHash().v2().toString();
dataDict[KEY_PROP_TIME_ELAPSED] = torrent->activeTime(); dataDict[KEY_PROP_TIME_ELAPSED] = torrent->activeTime();
dataDict[KEY_PROP_SEEDING_TIME] = torrent->seedingTime(); dataDict[KEY_PROP_SEEDING_TIME] = torrent->seedingTime();
dataDict[KEY_PROP_ETA] = static_cast<double>(torrent->eta()); dataDict[KEY_PROP_ETA] = static_cast<double>(torrent->eta());

4
src/webui/www/private/index.html

@ -166,8 +166,10 @@
<a href="#" class="arrow-right"><img src="icons/edit-copy.svg" alt="QBT_TR(Copy)QBT_TR[CONTEXT=TransferListWidget]" /> QBT_TR(Copy)QBT_TR[CONTEXT=TransferListWidget]</a> <a href="#" class="arrow-right"><img src="icons/edit-copy.svg" alt="QBT_TR(Copy)QBT_TR[CONTEXT=TransferListWidget]" /> QBT_TR(Copy)QBT_TR[CONTEXT=TransferListWidget]</a>
<ul> <ul>
<li><a href="#" id="copyName" class="copyToClipboard"><img src="icons/edit-copy.svg" alt="QBT_TR(Name)QBT_TR[CONTEXT=TransferListWidget]" /> QBT_TR(Name)QBT_TR[CONTEXT=TransferListWidget]</a></li> <li><a href="#" id="copyName" class="copyToClipboard"><img src="icons/edit-copy.svg" alt="QBT_TR(Name)QBT_TR[CONTEXT=TransferListWidget]" /> QBT_TR(Name)QBT_TR[CONTEXT=TransferListWidget]</a></li>
<li><a href="#" id="copyHash" class="copyToClipboard"><img src="icons/edit-copy.svg" alt="QBT_TR(Hash)QBT_TR[CONTEXT=TransferListWidget]" /> QBT_TR(Hash)QBT_TR[CONTEXT=TransferListWidget]</a></li> <li><a href="#" id="copyInfohash1" class="copyToClipboard"><img src="icons/edit-copy.svg" alt="QBT_TR(Info hash v1)QBT_TR[CONTEXT=TransferListWidget]" /> QBT_TR(Info hash v1)QBT_TR[CONTEXT=TransferListWidget]</a></li>
<li><a href="#" id="copyInfohash2" class="copyToClipboard"><img src="icons/edit-copy.svg" alt="QBT_TR(Info hash v2)QBT_TR[CONTEXT=TransferListWidget]" /> QBT_TR(Info hash v2)QBT_TR[CONTEXT=TransferListWidget]</a></li>
<li><a href="#" id="copyMagnetLink" class="copyToClipboard"><img src="icons/kt-magnet.svg" alt="QBT_TR(Magnet link)QBT_TR[CONTEXT=TransferListWidget]" /> QBT_TR(Magnet link)QBT_TR[CONTEXT=TransferListWidget]</a></li> <li><a href="#" id="copyMagnetLink" class="copyToClipboard"><img src="icons/kt-magnet.svg" alt="QBT_TR(Magnet link)QBT_TR[CONTEXT=TransferListWidget]" /> QBT_TR(Magnet link)QBT_TR[CONTEXT=TransferListWidget]</a></li>
<li><a href="#" id="copyID" class="copyToClipboard"><img src="icons/edit-copy.svg" alt="QBT_TR(Torrent ID)QBT_TR[CONTEXT=TransferListWidget]" /> QBT_TR(Torrent ID)QBT_TR[CONTEXT=TransferListWidget]</a></li>
</ul> </ul>
</li> </li>
</ul> </ul>

8
src/webui/www/private/scripts/client.js

@ -1201,10 +1201,14 @@ function setupCopyEventHandler() {
switch (trigger.id) { switch (trigger.id) {
case "copyName": case "copyName":
return copyNameFN(); return copyNameFN();
case "copyInfohash1":
return copyInfohashFN(1);
case "copyInfohash2":
return copyInfohashFN(2);
case "copyMagnetLink": case "copyMagnetLink":
return copyMagnetLinkFN(); return copyMagnetLinkFN();
case "copyHash": case "copyID":
return copyHashFN(); return copyIdFN();
default: default:
return ""; return "";
} }

2
src/webui/www/private/scripts/dynamicTable.js

@ -1383,7 +1383,7 @@ window.qBittorrent.DynamicTable = (function() {
tr.addClass("torrentsTableContextMenuTarget"); tr.addClass("torrentsTableContextMenuTarget");
}, },
getCurrentTorrentHash: function() { getCurrentTorrentID: function() {
return this.getSelectedRowId(); return this.getSelectedRowId();
}, },

34
src/webui/www/private/scripts/mocha-init.js

@ -84,8 +84,9 @@ let resumeTorrentsByTrackerFN = function() {};
let pauseTorrentsByTrackerFN = function() {}; let pauseTorrentsByTrackerFN = function() {};
let deleteTorrentsByTrackerFN = function() {}; let deleteTorrentsByTrackerFN = function() {};
let copyNameFN = function() {}; let copyNameFN = function() {};
let copyInfohashFN = function(policy) {};
let copyMagnetLinkFN = function() {}; let copyMagnetLinkFN = function() {};
let copyHashFN = function() {}; let copyIdFN = function() {};
let setQueuePositionFN = function() {}; let setQueuePositionFN = function() {};
const initializeWindows = function() { const initializeWindows = function() {
@ -905,7 +906,7 @@ const initializeWindows = function() {
copyNameFN = function() { copyNameFN = function() {
const selectedRows = torrentsTable.selectedRowsIds(); const selectedRows = torrentsTable.selectedRowsIds();
const names = []; const names = [];
if (selectedRows.length) { if (selectedRows.length > 0) {
const rows = torrentsTable.getFilteredAndSortedRows(); const rows = torrentsTable.getFilteredAndSortedRows();
for (let i = 0; i < selectedRows.length; ++i) { for (let i = 0; i < selectedRows.length; ++i) {
const hash = selectedRows[i]; const hash = selectedRows[i];
@ -915,10 +916,35 @@ const initializeWindows = function() {
return names.join("\n"); return names.join("\n");
}; };
copyInfohashFN = function(policy) {
const selectedRows = torrentsTable.selectedRowsIds();
const infohashes = [];
if (selectedRows.length > 0) {
const rows = torrentsTable.getFilteredAndSortedRows();
switch (policy) {
case 1:
for (const id of selectedRows) {
const infohash = rows[id].full_data.infohash_v1;
if (infohash !== "")
infohashes.push(infohash);
}
break;
case 2:
for (const id of selectedRows) {
const infohash = rows[id].full_data.infohash_v2;
if (infohash !== "")
infohashes.push(infohash);
}
break;
}
}
return infohashes.join("\n");
};
copyMagnetLinkFN = function() { copyMagnetLinkFN = function() {
const selectedRows = torrentsTable.selectedRowsIds(); const selectedRows = torrentsTable.selectedRowsIds();
const magnets = []; const magnets = [];
if (selectedRows.length) { if (selectedRows.length > 0) {
const rows = torrentsTable.getFilteredAndSortedRows(); const rows = torrentsTable.getFilteredAndSortedRows();
for (let i = 0; i < selectedRows.length; ++i) { for (let i = 0; i < selectedRows.length; ++i) {
const hash = selectedRows[i]; const hash = selectedRows[i];
@ -928,7 +954,7 @@ const initializeWindows = function() {
return magnets.join("\n"); return magnets.join("\n");
}; };
copyHashFN = function() { copyIdFN = function() {
return torrentsTable.selectedRowsIds().join("\n"); return torrentsTable.selectedRowsIds().join("\n");
}; };

4
src/webui/www/private/scripts/prop-files.js

@ -343,7 +343,7 @@ window.qBittorrent.PropFiles = (function() {
// Tab changed, don't do anything // Tab changed, don't do anything
return; return;
} }
const new_hash = torrentsTable.getCurrentTorrentHash(); const new_hash = torrentsTable.getCurrentTorrentID();
if (new_hash === "") { if (new_hash === "") {
torrentFilesTable.clear(); torrentFilesTable.clear();
clearTimeout(loadTorrentFilesDataTimer); clearTimeout(loadTorrentFilesDataTimer);
@ -527,7 +527,7 @@ window.qBittorrent.PropFiles = (function() {
menu: 'torrentFilesMenu', menu: 'torrentFilesMenu',
actions: { actions: {
Rename: function(element, ref) { Rename: function(element, ref) {
const hash = torrentsTable.getCurrentTorrentHash(); const hash = torrentsTable.getCurrentTorrentID();
if (!hash) return; if (!hash) return;
const rowId = torrentFilesTable.selectedRowsIds()[0]; const rowId = torrentFilesTable.selectedRowsIds()[0];
if (rowId === undefined) return; if (rowId === undefined) return;

23
src/webui/www/private/scripts/prop-general.js

@ -61,7 +61,8 @@ window.qBittorrent.PropGeneral = (function() {
$('addition_date').set('html', ''); $('addition_date').set('html', '');
$('completion_date').set('html', ''); $('completion_date').set('html', '');
$('creation_date').set('html', ''); $('creation_date').set('html', '');
$('torrent_hash').set('html', ''); $('torrent_hash_v1').set('html', '');
$('torrent_hash_v2').set('html', '');
$('save_path').set('html', ''); $('save_path').set('html', '');
$('comment').set('html', ''); $('comment').set('html', '');
}; };
@ -73,16 +74,14 @@ window.qBittorrent.PropGeneral = (function() {
// Tab changed, don't do anything // Tab changed, don't do anything
return; return;
} }
const current_hash = torrentsTable.getCurrentTorrentHash(); const current_id = torrentsTable.getCurrentTorrentID();
if (current_hash === "") { if (current_id === "") {
clearData(); clearData();
clearTimeout(loadTorrentDataTimer); clearTimeout(loadTorrentDataTimer);
loadTorrentDataTimer = loadTorrentData.delay(5000); loadTorrentDataTimer = loadTorrentData.delay(5000);
return; return;
} }
// Display hash const url = new URI('api/v2/torrents/properties?hash=' + current_id);
$('torrent_hash').set('html', current_hash);
const url = new URI('api/v2/torrents/properties?hash=' + current_hash);
new Request.JSON({ new Request.JSON({
url: url, url: url,
noCache: true, noCache: true,
@ -191,6 +190,18 @@ window.qBittorrent.PropGeneral = (function() {
temp = "QBT_TR(Unknown)QBT_TR[CONTEXT=HttpServer]"; temp = "QBT_TR(Unknown)QBT_TR[CONTEXT=HttpServer]";
$('creation_date').set('html', temp); $('creation_date').set('html', temp);
if (data.infohash_v1 === "")
temp = "QBT_TR(N/A)QBT_TR[CONTEXT=PropertiesWidget]";
else
temp = data.infohash_v1;
$('torrent_hash_v1').set('html', temp);
if (data.infohash_v2 === "")
temp = "QBT_TR(N/A)QBT_TR[CONTEXT=PropertiesWidget]";
else
temp = data.infohash_v2;
$('torrent_hash_v2').set('html', temp);
$('save_path').set('html', data.save_path); $('save_path').set('html', data.save_path);
$('comment').set('html', window.qBittorrent.Misc.parseHtmlLinks(window.qBittorrent.Misc.escapeHtml(data.comment))); $('comment').set('html', window.qBittorrent.Misc.parseHtmlLinks(window.qBittorrent.Misc.escapeHtml(data.comment)));

6
src/webui/www/private/scripts/prop-peers.js

@ -51,7 +51,7 @@ window.qBittorrent.PropPeers = (function() {
torrentPeersTable.clear(); torrentPeersTable.clear();
return; return;
} }
const current_hash = torrentsTable.getCurrentTorrentHash(); const current_hash = torrentsTable.getCurrentTorrentID();
if (current_hash === "") { if (current_hash === "") {
syncTorrentPeersLastResponseId = 0; syncTorrentPeersLastResponseId = 0;
torrentPeersTable.clear(); torrentPeersTable.clear();
@ -118,7 +118,7 @@ window.qBittorrent.PropPeers = (function() {
menu: 'torrentPeersMenu', menu: 'torrentPeersMenu',
actions: { actions: {
addPeer: function(element, ref) { addPeer: function(element, ref) {
const hash = torrentsTable.getCurrentTorrentHash(); const hash = torrentsTable.getCurrentTorrentID();
if (!hash) if (!hash)
return; return;
@ -147,7 +147,7 @@ window.qBittorrent.PropPeers = (function() {
noCache: true, noCache: true,
method: 'post', method: 'post',
data: { data: {
hash: torrentsTable.getCurrentTorrentHash(), hash: torrentsTable.getCurrentTorrentID(),
peers: selectedPeers.join('|') peers: selectedPeers.join('|')
} }
}).send(); }).send();

2
src/webui/www/private/scripts/prop-trackers.js

@ -50,7 +50,7 @@ window.qBittorrent.PropTrackers = (function() {
// Tab changed, don't do anything // Tab changed, don't do anything
return; return;
} }
const new_hash = torrentsTable.getCurrentTorrentHash(); const new_hash = torrentsTable.getCurrentTorrentID();
if (new_hash === "") { if (new_hash === "") {
torrentTrackersTable.clear(); torrentTrackersTable.clear();
clearTimeout(loadTrackersDataTimer); clearTimeout(loadTrackersDataTimer);

2
src/webui/www/private/scripts/prop-webseeds.js

@ -100,7 +100,7 @@ window.qBittorrent.PropWebseeds = (function() {
// Tab changed, don't do anything // Tab changed, don't do anything
return; return;
} }
const new_hash = torrentsTable.getCurrentTorrentHash(); const new_hash = torrentsTable.getCurrentTorrentID();
if (new_hash === "") { if (new_hash === "") {
wsTable.removeAllRows(); wsTable.removeAllRows();
clearTimeout(loadWebSeedsDataTimer); clearTimeout(loadWebSeedsDataTimer);

4
src/webui/www/private/views/preferences.html

@ -209,7 +209,9 @@
<li>QBT_TR(%C: Number of files)QBT_TR[CONTEXT=OptionsDialog]</li> <li>QBT_TR(%C: Number of files)QBT_TR[CONTEXT=OptionsDialog]</li>
<li>QBT_TR(%Z: Torrent size (bytes))QBT_TR[CONTEXT=OptionsDialog]</li> <li>QBT_TR(%Z: Torrent size (bytes))QBT_TR[CONTEXT=OptionsDialog]</li>
<li>QBT_TR(%T: Current tracker)QBT_TR[CONTEXT=OptionsDialog]</li> <li>QBT_TR(%T: Current tracker)QBT_TR[CONTEXT=OptionsDialog]</li>
<li>QBT_TR(%I: Info hash)QBT_TR[CONTEXT=OptionsDialog]</li> <li>QBT_TR(%I: Info hash v1)QBT_TR[CONTEXT=OptionsDialog]</li>
<li>QBT_TR(%J: Info hash v2)QBT_TR[CONTEXT=OptionsDialog]</li>
<li>QBT_TR(%K: Torrent ID)QBT_TR[CONTEXT=OptionsDialog]</li>
</ul> </ul>
QBT_TR(Tip: Encapsulate parameter with quotation marks to avoid text being cut off at whitespace (e.g., "%N"))QBT_TR[CONTEXT=OptionsDialog] QBT_TR(Tip: Encapsulate parameter with quotation marks to avoid text being cut off at whitespace (e.g., "%N"))QBT_TR[CONTEXT=OptionsDialog]
</div> </div>

8
src/webui/www/private/views/properties.html

@ -64,8 +64,12 @@
<td id="creation_date"></td> <td id="creation_date"></td>
</tr> </tr>
<tr> <tr>
<td class="generalLabel">QBT_TR(Torrent Hash:)QBT_TR[CONTEXT=PropertiesWidget]</td> <td class="generalLabel">QBT_TR(Info Hash v1:)QBT_TR[CONTEXT=PropertiesWidget]</td>
<td colspan="5" id="torrent_hash"></td> <td colspan="5" id="torrent_hash_v1"></td>
</tr>
<tr>
<td class="generalLabel">QBT_TR(Info Hash v2:)QBT_TR[CONTEXT=PropertiesWidget]</td>
<td colspan="5" id="torrent_hash_v2"></td>
</tr> </tr>
<tr> <tr>
<td class="generalLabel">QBT_TR(Save Path:)QBT_TR[CONTEXT=PropertiesWidget]</td> <td class="generalLabel">QBT_TR(Save Path:)QBT_TR[CONTEXT=PropertiesWidget]</td>

Loading…
Cancel
Save