diff --git a/src/base/bittorrent/torrent.h b/src/base/bittorrent/torrent.h index e3435d37e..34cccd4ff 100644 --- a/src/base/bittorrent/torrent.h +++ b/src/base/bittorrent/torrent.h @@ -282,6 +282,7 @@ namespace BitTorrent virtual int maxSeedingTime() const = 0; virtual int maxInactiveSeedingTime() const = 0; virtual qreal realRatio() const = 0; + virtual qreal popularity() const = 0; virtual int uploadPayloadRate() const = 0; virtual int downloadPayloadRate() const = 0; virtual qlonglong totalPayloadUpload() const = 0; diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index d37ff9df3..5a1be7a4e 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -1545,6 +1545,15 @@ qlonglong TorrentImpl::nextAnnounce() const return lt::total_seconds(m_nativeStatus.next_announce); } +qreal TorrentImpl::popularity() const +{ + // in order to produce floating-point numbers using `std::chrono::duration_cast`, + // we should use `qreal` as `Rep` to define the `months` duration + using months = std::chrono::duration; + const auto activeMonths = std::chrono::duration_cast(m_nativeStatus.active_duration).count(); + return (activeMonths > 0) ? (realRatio() / activeMonths) : 0; +} + void TorrentImpl::setName(const QString &name) { if (m_name != name) diff --git a/src/base/bittorrent/torrentimpl.h b/src/base/bittorrent/torrentimpl.h index 003a67e25..44907c164 100644 --- a/src/base/bittorrent/torrentimpl.h +++ b/src/base/bittorrent/torrentimpl.h @@ -210,6 +210,7 @@ namespace BitTorrent int maxSeedingTime() const override; int maxInactiveSeedingTime() const override; qreal realRatio() const override; + qreal popularity() const override; int uploadPayloadRate() const override; int downloadPayloadRate() const override; qlonglong totalPayloadUpload() const override; diff --git a/src/gui/properties/propertieswidget.cpp b/src/gui/properties/propertieswidget.cpp index b70df143c..980978cb9 100644 --- a/src/gui/properties/propertieswidget.cpp +++ b/src/gui/properties/propertieswidget.cpp @@ -220,6 +220,7 @@ void PropertiesWidget::clear() m_ui->labelConnectionsVal->clear(); m_ui->labelReannounceInVal->clear(); m_ui->labelShareRatioVal->clear(); + m_ui->labelPopularityVal->clear(); m_ui->listWebSeeds->clear(); m_ui->labelETAVal->clear(); m_ui->labelSeedsVal->clear(); @@ -407,6 +408,9 @@ void PropertiesWidget::loadDynamicData() const qreal ratio = m_torrent->realRatio(); m_ui->labelShareRatioVal->setText(ratio > BitTorrent::Torrent::MAX_RATIO ? C_INFINITY : Utils::String::fromDouble(ratio, 2)); + const qreal popularity = m_torrent->popularity(); + m_ui->labelPopularityVal->setText(popularity > BitTorrent::Torrent::MAX_RATIO ? C_INFINITY : Utils::String::fromDouble(popularity, 2)); + m_ui->labelSeedsVal->setText(tr("%1 (%2 total)", "%1 and %2 are numbers, e.g. 3 (10 total)") .arg(QString::number(m_torrent->seedsCount()) , QString::number(m_torrent->totalSeedsCount()))); diff --git a/src/gui/properties/propertieswidget.ui b/src/gui/properties/propertieswidget.ui index c973a4ea6..17ffa6b4f 100644 --- a/src/gui/properties/propertieswidget.ui +++ b/src/gui/properties/propertieswidget.ui @@ -295,6 +295,25 @@ + + + + + 0 + 0 + + + + Ratio / Time Active (in months), indicates how popular the torrent is + + + Popularity: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + @@ -495,6 +514,22 @@ + + + + + 0 + 0 + + + + Ratio / Time Active (in months), indicates how popular the torrent is + + + Qt::PlainText + + + diff --git a/src/gui/transferlistmodel.cpp b/src/gui/transferlistmodel.cpp index 1870dc05d..cb35a51c0 100644 --- a/src/gui/transferlistmodel.cpp +++ b/src/gui/transferlistmodel.cpp @@ -167,6 +167,7 @@ QVariant TransferListModel::headerData(const int section, const Qt::Orientation case TR_DLSPEED: return tr("Down Speed", "i.e: Download speed"); case TR_UPSPEED: return tr("Up Speed", "i.e: Upload speed"); case TR_RATIO: return tr("Ratio", "Share ratio"); + case TR_POPULARITY: return tr("Popularity"); case TR_ETA: return tr("ETA", "i.e: Estimated Time of Arrival / Time left"); case TR_CATEGORY: return tr("Category"); case TR_TAGS: return tr("Tags"); @@ -195,6 +196,14 @@ QVariant TransferListModel::headerData(const int section, const Qt::Orientation default: return {}; } } + else if (role == Qt::ToolTipRole) + { + switch (section) + { + case TR_POPULARITY: return tr("Ratio / Time Active (in months), indicates how popular the torrent is"); + default: return {}; + } + } else if (role == Qt::TextAlignmentRole) { switch (section) @@ -216,6 +225,7 @@ QVariant TransferListModel::headerData(const int section, const Qt::Orientation case TR_DLLIMIT: case TR_RATIO_LIMIT: case TR_RATIO: + case TR_POPULARITY: case TR_QUEUE_POSITION: case TR_LAST_ACTIVITY: case TR_AVAILABILITY: @@ -373,6 +383,8 @@ QString TransferListModel::displayValue(const BitTorrent::Torrent *torrent, cons return ratioString(torrent->realRatio()); case TR_RATIO_LIMIT: return ratioString(torrent->maxRatio()); + case TR_POPULARITY: + return ratioString(torrent->popularity()); case TR_CATEGORY: return torrent->category(); case TR_TAGS: @@ -450,6 +462,8 @@ QVariant TransferListModel::internalValue(const BitTorrent::Torrent *torrent, co return torrent->eta(); case TR_RATIO: return torrent->realRatio(); + case TR_POPULARITY: + return torrent->popularity(); case TR_CATEGORY: return torrent->category(); case TR_TAGS: @@ -559,6 +573,7 @@ QVariant TransferListModel::data(const QModelIndex &index, const int role) const case TR_DLLIMIT: case TR_RATIO_LIMIT: case TR_RATIO: + case TR_POPULARITY: case TR_QUEUE_POSITION: case TR_LAST_ACTIVITY: case TR_AVAILABILITY: diff --git a/src/gui/transferlistmodel.h b/src/gui/transferlistmodel.h index 2f00d1339..061db4683 100644 --- a/src/gui/transferlistmodel.h +++ b/src/gui/transferlistmodel.h @@ -62,6 +62,7 @@ public: TR_UPSPEED, TR_ETA, TR_RATIO, + TR_POPULARITY, TR_CATEGORY, TR_TAGS, TR_ADD_DATE, diff --git a/src/gui/transferlistsortmodel.cpp b/src/gui/transferlistsortmodel.cpp index 6610e9933..2f9ce6ff0 100644 --- a/src/gui/transferlistsortmodel.cpp +++ b/src/gui/transferlistsortmodel.cpp @@ -196,6 +196,7 @@ int TransferListSortModel::compare(const QModelIndex &left, const QModelIndex &r case TransferListModel::TR_PROGRESS: case TransferListModel::TR_RATIO: case TransferListModel::TR_RATIO_LIMIT: + case TransferListModel::TR_POPULARITY: return customCompare(leftValue.toReal(), rightValue.toReal()); case TransferListModel::TR_STATUS: diff --git a/src/gui/transferlistwidget.cpp b/src/gui/transferlistwidget.cpp index 2540cea03..c0ca4371d 100644 --- a/src/gui/transferlistwidget.cpp +++ b/src/gui/transferlistwidget.cpp @@ -178,6 +178,7 @@ TransferListWidget::TransferListWidget(QWidget *parent, MainWindow *mainWindow) setColumnHidden(TransferListModel::TR_INFOHASH_V2, true); setColumnHidden(TransferListModel::TR_COMPLETED, true); setColumnHidden(TransferListModel::TR_RATIO_LIMIT, true); + setColumnHidden(TransferListModel::TR_POPULARITY, true); setColumnHidden(TransferListModel::TR_SEEN_COMPLETE_DATE, true); setColumnHidden(TransferListModel::TR_LAST_ACTIVITY, true); setColumnHidden(TransferListModel::TR_TOTAL_SIZE, true); @@ -696,6 +697,7 @@ void TransferListWidget::displayColumnHeaderMenu() continue; const auto columnName = m_listModel->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); + const QVariant columnToolTip = m_listModel->headerData(i, Qt::Horizontal, Qt::ToolTipRole); QAction *action = menu->addAction(columnName, this, [this, i](const bool checked) { if (!checked && (visibleColumnsCount() <= 1)) @@ -710,6 +712,8 @@ void TransferListWidget::displayColumnHeaderMenu() }); action->setCheckable(true); action->setChecked(!isColumnHidden(i)); + if (!columnToolTip.isNull()) + action->setToolTip(columnToolTip.toString()); } menu->addSeparator(); diff --git a/src/webui/api/serialize/serialize_torrent.cpp b/src/webui/api/serialize/serialize_torrent.cpp index 28c821d72..9f7416456 100644 --- a/src/webui/api/serialize/serialize_torrent.cpp +++ b/src/webui/api/serialize/serialize_torrent.cpp @@ -152,6 +152,7 @@ QVariantMap serialize(const BitTorrent::Torrent &torrent) {KEY_TORRENT_MAX_INACTIVE_SEEDING_TIME, torrent.maxInactiveSeedingTime()}, {KEY_TORRENT_RATIO, adjustRatio(torrent.realRatio())}, {KEY_TORRENT_RATIO_LIMIT, torrent.ratioLimit()}, + {KEY_TORRENT_POPULARITY, torrent.popularity()}, {KEY_TORRENT_SEEDING_TIME_LIMIT, torrent.seedingTimeLimit()}, {KEY_TORRENT_INACTIVE_SEEDING_TIME_LIMIT, torrent.inactiveSeedingTimeLimit()}, {KEY_TORRENT_LAST_SEEN_COMPLETE_TIME, Utils::DateTime::toSecsSinceEpoch(torrent.lastSeenComplete())}, diff --git a/src/webui/api/serialize/serialize_torrent.h b/src/webui/api/serialize/serialize_torrent.h index 1a8e8e3fb..db37f087e 100644 --- a/src/webui/api/serialize/serialize_torrent.h +++ b/src/webui/api/serialize/serialize_torrent.h @@ -54,6 +54,7 @@ inline const QString KEY_TORRENT_NUM_COMPLETE = u"num_complete"_s; inline const QString KEY_TORRENT_LEECHS = u"num_leechs"_s; inline const QString KEY_TORRENT_NUM_INCOMPLETE = u"num_incomplete"_s; inline const QString KEY_TORRENT_RATIO = u"ratio"_s; +inline const QString KEY_TORRENT_POPULARITY = u"popularity"_s; inline const QString KEY_TORRENT_ETA = u"eta"_s; inline const QString KEY_TORRENT_STATE = u"state"_s; inline const QString KEY_TORRENT_SEQUENTIAL_DOWNLOAD = u"seq_dl"_s; diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index 76e0a07d7..2d40d3645 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -97,6 +97,7 @@ const QString KEY_PROP_SEEDS_TOTAL = u"seeds_total"_s; const QString KEY_PROP_PEERS = u"peers"_s; const QString KEY_PROP_PEERS_TOTAL = u"peers_total"_s; const QString KEY_PROP_RATIO = u"share_ratio"_s; +const QString KEY_PROP_POPULARITY = u"popularity"_s; const QString KEY_PROP_REANNOUNCE = u"reannounce"_s; const QString KEY_PROP_TOTAL_SIZE = u"total_size"_s; const QString KEY_PROP_PIECES_NUM = u"pieces_num"_s; @@ -398,6 +399,7 @@ void TorrentsController::infoAction() // - "peers": Torrent connected peers // - "peers_total": Torrent total number of peers // - "share_ratio": Torrent share ratio +// - "popularity": Torrent popularity // - "reannounce": Torrent next reannounce time // - "total_size": Torrent total size // - "pieces_num": Torrent pieces count @@ -432,6 +434,7 @@ void TorrentsController::propertiesAction() const int downloadLimit = torrent->downloadLimit(); const int uploadLimit = torrent->uploadLimit(); const qreal ratio = torrent->realRatio(); + const qreal popularity = torrent->popularity(); const QJsonObject ret { @@ -460,6 +463,7 @@ void TorrentsController::propertiesAction() {KEY_PROP_PEERS, torrent->leechsCount()}, {KEY_PROP_PEERS_TOTAL, torrent->totalLeechersCount()}, {KEY_PROP_RATIO, ((ratio > BitTorrent::Torrent::MAX_RATIO) ? -1 : ratio)}, + {KEY_PROP_POPULARITY, ((popularity > BitTorrent::Torrent::MAX_RATIO) ? -1 : popularity)}, {KEY_PROP_REANNOUNCE, torrent->nextAnnounce()}, {KEY_PROP_TOTAL_SIZE, torrent->totalSize()}, {KEY_PROP_PIECES_NUM, torrent->piecesCount()}, diff --git a/src/webui/www/private/scripts/dynamicTable.js b/src/webui/www/private/scripts/dynamicTable.js index c01d72cd7..b8afaf20f 100644 --- a/src/webui/www/private/scripts/dynamicTable.js +++ b/src/webui/www/private/scripts/dynamicTable.js @@ -926,6 +926,7 @@ window.qBittorrent.DynamicTable = (function() { this.newColumn('upspeed', '', 'QBT_TR(Up Speed)QBT_TR[CONTEXT=TransferListModel]', 100, true); this.newColumn('eta', '', 'QBT_TR(ETA)QBT_TR[CONTEXT=TransferListModel]', 100, true); this.newColumn('ratio', '', 'QBT_TR(Ratio)QBT_TR[CONTEXT=TransferListModel]', 100, true); + this.newColumn('popularity', '', 'QBT_TR(Popularity)QBT_TR[CONTEXT=TransferListModel]', 100, true); this.newColumn('category', '', 'QBT_TR(Category)QBT_TR[CONTEXT=TransferListModel]', 100, true); this.newColumn('tags', '', 'QBT_TR(Tags)QBT_TR[CONTEXT=TransferListModel]', 100, true); this.newColumn('added_on', '', 'QBT_TR(Added On)QBT_TR[CONTEXT=TransferListModel]', 100, true); @@ -1228,6 +1229,14 @@ window.qBittorrent.DynamicTable = (function() { td.set('title', string); }; + // popularity + this.columns['popularity'].updateTd = function(td, row) { + const value = this.getRowValue(row); + const popularity = (value === -1) ? '∞' : window.qBittorrent.Misc.toFixedPointString(value, 2); + td.set('text', popularity); + td.set('title', popularity); + }; + // added on this.columns['added_on'].updateTd = function(td, row) { const date = new Date(this.getRowValue(row) * 1000).toLocaleString(); diff --git a/src/webui/www/private/scripts/prop-general.js b/src/webui/www/private/scripts/prop-general.js index 700a52651..82d1c9b8a 100644 --- a/src/webui/www/private/scripts/prop-general.js +++ b/src/webui/www/private/scripts/prop-general.js @@ -58,6 +58,7 @@ window.qBittorrent.PropGeneral = (function() { $('seeds').set('html', ''); $('peers').set('html', ''); $('share_ratio').set('html', ''); + $('popularity').set('html', ''); $('reannounce').set('html', ''); $('last_seen').set('html', ''); $('total_size').set('html', ''); @@ -160,6 +161,8 @@ window.qBittorrent.PropGeneral = (function() { $('share_ratio').set('html', data.share_ratio.toFixed(2)); + $('popularity').set('html', data.popularity.toFixed(2)); + $('reannounce').set('html', window.qBittorrent.Misc.friendlyDuration(data.reannounce)); const lastSeen = (data.last_seen >= 0) diff --git a/src/webui/www/private/views/properties.html b/src/webui/www/private/views/properties.html index dc3a3bf83..473221ed1 100644 --- a/src/webui/www/private/views/properties.html +++ b/src/webui/www/private/views/properties.html @@ -49,6 +49,10 @@ QBT_TR(Last Seen Complete:)QBT_TR[CONTEXT=PropertiesWidget] + + QBT_TR(Popularity:)QBT_TR[CONTEXT=PropertiesWidget] + +