Add ability to display torrent "privateness" in UI

PR #20951.

---------

Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
Co-authored-by: Vladimir Golovnev <glassez@yandex.ru>
Co-authored-by: thalieht <thalieht@users.noreply.github.com>
This commit is contained in:
ManiMatter 2024-07-07 18:09:57 +02:00 committed by GitHub
parent a4f63a5c30
commit 87a202c71e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 183 additions and 85 deletions

View file

@ -210,6 +210,7 @@ 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->labelPrivateVal->clear();
m_ui->labelInfohash1Val->clear(); m_ui->labelInfohash1Val->clear();
m_ui->labelInfohash2Val->clear(); m_ui->labelInfohash2Val->clear();
m_ui->labelCommentVal->clear(); m_ui->labelCommentVal->clear();
@ -335,7 +336,14 @@ void PropertiesWidget::loadTorrentInfos(BitTorrent::Torrent *const torrent)
m_ui->labelCommentVal->setText(Utils::Misc::parseHtmlLinks(m_torrent->comment().toHtmlEscaped())); m_ui->labelCommentVal->setText(Utils::Misc::parseHtmlLinks(m_torrent->comment().toHtmlEscaped()));
m_ui->labelCreatedByVal->setText(m_torrent->creator()); m_ui->labelCreatedByVal->setText(m_torrent->creator());
m_ui->labelPrivateVal->setText(m_torrent->isPrivate() ? tr("Yes") : tr("No"));
} }
else
{
m_ui->labelPrivateVal->setText(tr("N/A"));
}
// Load dynamic data // Load dynamic data
loadDynamicData(); loadDynamicData();
} }

View file

@ -823,6 +823,38 @@
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel" name="labelPrivate">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Private:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="1" colspan="5">
<widget class="QLabel" name="labelPrivateVal">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="labelInfohash1"> <widget class="QLabel" name="labelInfohash1">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred"> <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
@ -838,71 +870,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0">
<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"> <item row="3" column="1" colspan="5">
<widget class="QLabel" name="labelInfohash2Val">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="labelSavePath">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Save Path:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
</property>
</widget>
</item>
<item row="5" column="0">
<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"> <widget class="QLabel" name="labelInfohash1Val">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred"> <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
@ -918,7 +886,55 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0">
<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="4" column="1" colspan="5"> <item row="4" column="1" colspan="5">
<widget class="QLabel" name="labelInfohash2Val">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="labelSavePath">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Save Path:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
</property>
</widget>
</item>
<item row="5" column="1" colspan="5">
<widget class="QLabel" name="labelSavePathVal"> <widget class="QLabel" name="labelSavePathVal">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred"> <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
@ -937,7 +953,23 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="1" colspan="5"> <item row="6" column="0">
<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="6" 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">

View file

@ -193,6 +193,7 @@ QVariant TransferListModel::headerData(const int section, const Qt::Orientation
case TR_INFOHASH_V1: return tr("Info Hash v1", "i.e: torrent info hash v1"); case TR_INFOHASH_V1: return tr("Info Hash v1", "i.e: torrent info hash v1");
case TR_INFOHASH_V2: return tr("Info Hash v2", "i.e: torrent info hash v2"); case TR_INFOHASH_V2: return tr("Info Hash v2", "i.e: torrent info hash v2");
case TR_REANNOUNCE: return tr("Reannounce In", "Indicates the time until next trackers reannounce"); case TR_REANNOUNCE: return tr("Reannounce In", "Indicates the time until next trackers reannounce");
case TR_PRIVATE: return tr("Private", "Flags private torrents");
default: return {}; default: return {};
} }
} }
@ -357,6 +358,15 @@ QString TransferListModel::displayValue(const BitTorrent::Torrent *torrent, cons
return Utils::Misc::userFriendlyDuration(time); return Utils::Misc::userFriendlyDuration(time);
}; };
const auto privateString = [hideValues](const bool isPrivate, const bool hasMetadata) -> QString
{
if (hideValues && !isPrivate)
return {};
if (hasMetadata)
return isPrivate ? tr("Yes") : tr("No");
return tr("N/A");
};
switch (column) switch (column)
{ {
case TR_NAME: case TR_NAME:
@ -431,6 +441,8 @@ QString TransferListModel::displayValue(const BitTorrent::Torrent *torrent, cons
return hashString(torrent->infoHash().v2()); return hashString(torrent->infoHash().v2());
case TR_REANNOUNCE: case TR_REANNOUNCE:
return reannounceString(torrent->nextAnnounce()); return reannounceString(torrent->nextAnnounce());
case TR_PRIVATE:
return privateString(torrent->isPrivate(), torrent->hasMetadata());
} }
return {}; return {};
@ -512,6 +524,8 @@ QVariant TransferListModel::internalValue(const BitTorrent::Torrent *torrent, co
return QVariant::fromValue(torrent->infoHash().v2()); return QVariant::fromValue(torrent->infoHash().v2());
case TR_REANNOUNCE: case TR_REANNOUNCE:
return torrent->nextAnnounce(); return torrent->nextAnnounce();
case TR_PRIVATE:
return (torrent->hasMetadata() ? torrent->isPrivate() : QVariant());
} }
return {}; return {};

View file

@ -86,6 +86,7 @@ public:
TR_INFOHASH_V1, TR_INFOHASH_V1,
TR_INFOHASH_V2, TR_INFOHASH_V2,
TR_REANNOUNCE, TR_REANNOUNCE,
TR_PRIVATE,
NB_COLUMNS NB_COLUMNS
}; };

View file

@ -28,6 +28,7 @@
#include "transferlistsortmodel.h" #include "transferlistsortmodel.h"
#include <concepts>
#include <type_traits> #include <type_traits>
#include <QDateTime> #include <QDateTime>
@ -46,16 +47,6 @@ namespace
return (left < right) ? -1 : 1; return (left < right) ? -1 : 1;
} }
int customCompare(const QDateTime &left, const QDateTime &right)
{
const bool isLeftValid = left.isValid();
const bool isRightValid = right.isValid();
if (isLeftValid == isRightValid)
return threeWayCompare(left, right);
return isLeftValid ? -1 : 1;
}
int customCompare(const TagSet &left, const TagSet &right, const Utils::Compare::NaturalCompare<Qt::CaseInsensitive> &compare) int customCompare(const TagSet &left, const TagSet &right, const Utils::Compare::NaturalCompare<Qt::CaseInsensitive> &compare)
{ {
for (auto leftIter = left.cbegin(), rightIter = right.cbegin(); for (auto leftIter = left.cbegin(), rightIter = right.cbegin();
@ -70,12 +61,27 @@ namespace
} }
template <typename T> template <typename T>
int customCompare(const T left, const T right) concept Validateable = requires (T t) { {t.isValid()} -> std::same_as<bool>; };
{
static_assert(std::is_arithmetic_v<T>);
const bool isLeftValid = (left >= 0); template <Validateable T>
const bool isRightValid = (right >= 0); bool isValid(const T &value)
{
return value.isValid();
}
// consider negative values as invalid
template <typename T>
requires std::is_arithmetic_v<T>
bool isValid(const T value)
{
return (value >= 0);
}
template <typename T>
int customCompare(const T &left, const T &right)
{
const bool isLeftValid = isValid(left);
const bool isRightValid = isValid(right);
if (isLeftValid && isRightValid) if (isLeftValid && isRightValid)
return threeWayCompare(left, right); return threeWayCompare(left, right);
@ -209,6 +215,7 @@ int TransferListSortModel::compare(const QModelIndex &left, const QModelIndex &r
case TransferListModel::TR_DLLIMIT: case TransferListModel::TR_DLLIMIT:
case TransferListModel::TR_DLSPEED: case TransferListModel::TR_DLSPEED:
case TransferListModel::TR_PRIVATE:
case TransferListModel::TR_QUEUE_POSITION: case TransferListModel::TR_QUEUE_POSITION:
case TransferListModel::TR_UPLIMIT: case TransferListModel::TR_UPLIMIT:
case TransferListModel::TR_UPSPEED: case TransferListModel::TR_UPSPEED:

View file

@ -184,6 +184,7 @@ TransferListWidget::TransferListWidget(QWidget *parent, MainWindow *mainWindow)
setColumnHidden(TransferListModel::TR_LAST_ACTIVITY, true); setColumnHidden(TransferListModel::TR_LAST_ACTIVITY, true);
setColumnHidden(TransferListModel::TR_TOTAL_SIZE, true); setColumnHidden(TransferListModel::TR_TOTAL_SIZE, true);
setColumnHidden(TransferListModel::TR_REANNOUNCE, true); setColumnHidden(TransferListModel::TR_REANNOUNCE, true);
setColumnHidden(TransferListModel::TR_PRIVATE, true);
} }
//Ensure that at least one column is visible at all times //Ensure that at least one column is visible at all times

View file

@ -163,7 +163,8 @@ QVariantMap serialize(const BitTorrent::Torrent &torrent)
{KEY_TORRENT_AVAILABILITY, torrent.distributedCopies()}, {KEY_TORRENT_AVAILABILITY, torrent.distributedCopies()},
{KEY_TORRENT_REANNOUNCE, torrent.nextAnnounce()}, {KEY_TORRENT_REANNOUNCE, torrent.nextAnnounce()},
{KEY_TORRENT_COMMENT, torrent.comment()}, {KEY_TORRENT_COMMENT, torrent.comment()},
{KEY_TORRENT_PRIVATE, torrent.isPrivate()}, {KEY_TORRENT_PRIVATE, (torrent.hasMetadata() ? torrent.isPrivate() : QVariant())},
{KEY_TORRENT_TOTAL_SIZE, torrent.totalSize()} {KEY_TORRENT_TOTAL_SIZE, torrent.totalSize()},
{KEY_TORRENT_HAS_METADATA, torrent.hasMetadata()}
}; };
} }

View file

@ -94,5 +94,6 @@ inline const QString KEY_TORRENT_AVAILABILITY = u"availability"_s;
inline const QString KEY_TORRENT_REANNOUNCE = u"reannounce"_s; inline const QString KEY_TORRENT_REANNOUNCE = u"reannounce"_s;
inline const QString KEY_TORRENT_COMMENT = u"comment"_s; inline const QString KEY_TORRENT_COMMENT = u"comment"_s;
inline const QString KEY_TORRENT_PRIVATE = u"private"_s; inline const QString KEY_TORRENT_PRIVATE = u"private"_s;
inline const QString KEY_TORRENT_HAS_METADATA = u"has_metadata"_s;
QVariantMap serialize(const BitTorrent::Torrent &torrent); QVariantMap serialize(const BitTorrent::Torrent &torrent);

View file

@ -116,6 +116,8 @@ const QString KEY_PROP_PRIVATE = u"private"_s;
const QString KEY_PROP_SSL_CERTIFICATE = u"ssl_certificate"_s; const QString KEY_PROP_SSL_CERTIFICATE = u"ssl_certificate"_s;
const QString KEY_PROP_SSL_PRIVATEKEY = u"ssl_private_key"_s; const QString KEY_PROP_SSL_PRIVATEKEY = u"ssl_private_key"_s;
const QString KEY_PROP_SSL_DHPARAMS = u"ssl_dh_params"_s; const QString KEY_PROP_SSL_DHPARAMS = u"ssl_dh_params"_s;
const QString KEY_PROP_HAS_METADATA = u"has_metadata"_s;
// File keys // File keys
const QString KEY_FILE_INDEX = u"index"_s; const QString KEY_FILE_INDEX = u"index"_s;
@ -438,6 +440,8 @@ void TorrentsController::propertiesAction()
const int uploadLimit = torrent->uploadLimit(); const int uploadLimit = torrent->uploadLimit();
const qreal ratio = torrent->realRatio(); const qreal ratio = torrent->realRatio();
const qreal popularity = torrent->popularity(); const qreal popularity = torrent->popularity();
const bool hasMetadata = torrent->hasMetadata();
const bool isPrivate = torrent->isPrivate();
const QJsonObject ret const QJsonObject ret
{ {
@ -474,14 +478,15 @@ void TorrentsController::propertiesAction()
{KEY_PROP_PIECES_HAVE, torrent->piecesHave()}, {KEY_PROP_PIECES_HAVE, torrent->piecesHave()},
{KEY_PROP_CREATED_BY, torrent->creator()}, {KEY_PROP_CREATED_BY, torrent->creator()},
{KEY_PROP_IS_PRIVATE, torrent->isPrivate()}, // used for maintaining backward compatibility {KEY_PROP_IS_PRIVATE, torrent->isPrivate()}, // used for maintaining backward compatibility
{KEY_PROP_PRIVATE, torrent->isPrivate()}, {KEY_PROP_PRIVATE, (hasMetadata ? isPrivate : QJsonValue())},
{KEY_PROP_ADDITION_DATE, Utils::DateTime::toSecsSinceEpoch(torrent->addedTime())}, {KEY_PROP_ADDITION_DATE, Utils::DateTime::toSecsSinceEpoch(torrent->addedTime())},
{KEY_PROP_LAST_SEEN, Utils::DateTime::toSecsSinceEpoch(torrent->lastSeenComplete())}, {KEY_PROP_LAST_SEEN, Utils::DateTime::toSecsSinceEpoch(torrent->lastSeenComplete())},
{KEY_PROP_COMPLETION_DATE, Utils::DateTime::toSecsSinceEpoch(torrent->completedTime())}, {KEY_PROP_COMPLETION_DATE, Utils::DateTime::toSecsSinceEpoch(torrent->completedTime())},
{KEY_PROP_CREATION_DATE, Utils::DateTime::toSecsSinceEpoch(torrent->creationDate())}, {KEY_PROP_CREATION_DATE, Utils::DateTime::toSecsSinceEpoch(torrent->creationDate())},
{KEY_PROP_SAVE_PATH, torrent->savePath().toString()}, {KEY_PROP_SAVE_PATH, torrent->savePath().toString()},
{KEY_PROP_DOWNLOAD_PATH, torrent->downloadPath().toString()}, {KEY_PROP_DOWNLOAD_PATH, torrent->downloadPath().toString()},
{KEY_PROP_COMMENT, torrent->comment()} {KEY_PROP_COMMENT, torrent->comment()},
{KEY_PROP_HAS_METADATA, torrent->hasMetadata()}
}; };
setResult(ret); setResult(ret);

View file

@ -54,7 +54,7 @@
#include "base/utils/version.h" #include "base/utils/version.h"
#include "api/isessionmanager.h" #include "api/isessionmanager.h"
inline const Utils::Version<3, 2> API_VERSION {2, 11, 1}; inline const Utils::Version<3, 2> API_VERSION {2, 11, 2};
class QTimer; class QTimer;

View file

@ -948,6 +948,7 @@ window.qBittorrent.DynamicTable = (function() {
this.newColumn("last_activity", "", "QBT_TR(Last Activity)QBT_TR[CONTEXT=TransferListModel]", 100, false); this.newColumn("last_activity", "", "QBT_TR(Last Activity)QBT_TR[CONTEXT=TransferListModel]", 100, false);
this.newColumn("availability", "", "QBT_TR(Availability)QBT_TR[CONTEXT=TransferListModel]", 100, false); this.newColumn("availability", "", "QBT_TR(Availability)QBT_TR[CONTEXT=TransferListModel]", 100, false);
this.newColumn("reannounce", "", "QBT_TR(Reannounce In)QBT_TR[CONTEXT=TransferListModel]", 100, false); this.newColumn("reannounce", "", "QBT_TR(Reannounce In)QBT_TR[CONTEXT=TransferListModel]", 100, false);
this.newColumn("private", "", "QBT_TR(Private)QBT_TR[CONTEXT=TransferListModel]", 100, false);
this.columns["state_icon"].onclick = ""; this.columns["state_icon"].onclick = "";
this.columns["state_icon"].dataProperties[0] = "state"; this.columns["state_icon"].dataProperties[0] = "state";
@ -1331,6 +1332,19 @@ window.qBittorrent.DynamicTable = (function() {
td.set("text", time); td.set("text", time);
td.set("title", time); td.set("title", time);
}; };
// private
this.columns["private"].updateTd = function(td, row) {
const hasMetadata = row["full_data"].has_metadata;
const isPrivate = this.getRowValue(row);
const string = hasMetadata
? (isPrivate
? "QBT_TR(Yes)QBT_TR[CONTEXT=PropertiesWidget]"
: "QBT_TR(No)QBT_TR[CONTEXT=PropertiesWidget]")
: "QBT_TR(N/A)QBT_TR[CONTEXT=PropertiesWidget]";
td.set("text", string);
td.set("title", string);
};
}, },
applyFilter: function(row, filterName, categoryHash, tagHash, trackerHash, filterTerms) { applyFilter: function(row, filterName, categoryHash, tagHash, trackerHash, filterTerms) {

View file

@ -70,6 +70,7 @@ window.qBittorrent.PropGeneral = (function() {
$("torrent_hash_v2").set("html", ""); $("torrent_hash_v2").set("html", "");
$("save_path").set("html", ""); $("save_path").set("html", "");
$("comment").set("html", ""); $("comment").set("html", "");
$("private").set("html", "");
piecesBar.clear(); piecesBar.clear();
}; };
@ -210,6 +211,15 @@ window.qBittorrent.PropGeneral = (function() {
$("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)));
if (data.has_metadata) {
$("private").set("text", (data.private
? "QBT_TR(Yes)QBT_TR[CONTEXT=PropertiesWidget]"
: "QBT_TR(No)QBT_TR[CONTEXT=PropertiesWidget]"));
}
else {
$("private").set("text", "QBT_TR(N/A)QBT_TR[CONTEXT=PropertiesWidget]");
}
} }
else { else {
clearData(); clearData();

View file

@ -74,6 +74,10 @@
<td class="generalLabel">QBT_TR(Created On:)QBT_TR[CONTEXT=PropertiesWidget]</td> <td class="generalLabel">QBT_TR(Created On:)QBT_TR[CONTEXT=PropertiesWidget]</td>
<td id="creation_date"></td> <td id="creation_date"></td>
</tr> </tr>
<tr>
<td class="generalLabel">QBT_TR(Private:)QBT_TR[CONTEXT=PropertiesWidget]</td>
<td id="private"></td>
</tr>
<tr> <tr>
<td class="generalLabel">QBT_TR(Info Hash v1:)QBT_TR[CONTEXT=PropertiesWidget]</td> <td class="generalLabel">QBT_TR(Info Hash v1:)QBT_TR[CONTEXT=PropertiesWidget]</td>
<td colspan="5" id="torrent_hash_v1"></td> <td colspan="5" id="torrent_hash_v1"></td>