consolidate tracker useragents in a summary table

This commit is contained in:
Spine
2025-09-01 04:49:13 +00:00
parent 887dfe7747
commit 540e520e00
9 changed files with 107 additions and 16 deletions

View File

@@ -62,6 +62,17 @@ class User extends \Gazelle\BaseObject {
return $this->commentTotal[$page] ?? 0;
}
public function historyUseragentTracker(): array {
$result = $this->pg()->executeParams('
select useragent, total
from history_useragent_tracker
where id_user = $1
order by useragent
', $this->id
);
return $result->fetchAll(\PGSQL_ASSOC);
}
/**
* @see \Gazelle\Stats\Users::refresh()
*/

View File

@@ -678,6 +678,30 @@ class Users extends \Gazelle\Base {
return $processed;
}
public function refreshUseragentTracker(): int {
// NB: useragents may be null if the initial announce from ocelot
// was lost, e.g. because of a deadlock or queue overflow.
$result = $this->pg()->execute("
merge into history_useragent_tracker hut using (
select xfu.uid as id_user,
coalesce(xfu.useragent, '') as useragent,
count(*) as total
from relay.xbt_files_users xfu
group by xfu.uid, xfu.useragent
) as i on hut.id_user = i.id_user
and hut.useragent = i.useragent
when not matched and i.total > 0 then
insert ( id_user, useragent, total)
values (i.id_user, i.useragent, i.total)
when matched and i.total > 0 then
update set
total = i.total
when matched then
delete
");
return $result->getAffectedRows();
}
public function registerActivity(string $tableName, int $days): int {
if ($days > 0) {
self::$db->prepared_query("

View File

@@ -2,9 +2,14 @@
namespace Gazelle\Task;
use Gazelle\Stats\Users as UsersStats;
use Gazelle\Stats\TGroups as TGroupsStats;
class CommunityStats extends \Gazelle\Task {
public function run(): void {
$this->processed = new \Gazelle\Stats\Users()->refresh()
+ new \Gazelle\Stats\TGroups()->refresh();
$usersStats = new UsersStats();
$this->processed = new TGroupsStats()->refresh()
+ $usersStats->refresh()
+ $usersStats->refreshUseragentTracker();
}
}

View File

@@ -1389,14 +1389,6 @@ class User extends BaseAttrObject {
return $new;
}
public function clients(): array {
self::$db->prepared_query('
SELECT DISTINCT useragent FROM xbt_files_users WHERE uid = ?
', $this->id
);
return self::$db->collect(0) ?: ['None'];
}
protected function getSingleValue($cacheKey, $query): string {
$cacheKey .= '_' . $this->id;
if ($this->forceCacheFlush || ($value = self::$cache->get_value($cacheKey)) === false) {

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class TorrentClientUseragent extends AbstractMigration {
public function up(): void {
$this->table('history_useragent_tracker', ['id' => false, 'primary_key' => 'id_history_useragent_tracker'])
->addColumn('id_history_useragent_tracker', 'integer', ['identity' => true])
->addColumn('id_user', 'integer')
->addColumn('total', 'integer')
->addColumn('useragent', 'string', ['length' => 100])
->save();
$this->execute("
create index hut_u_idx on history_useragent_tracker (id_user)
");
}
public function down(): void {
$this->table('history_useragent_tracker')->drop()->save();
}
}

View File

@@ -123,7 +123,14 @@ Last seen: {{ user.lastAccessRealtime }}
Up: {{ user.uploadedSize|octet_size(3) }}
Down: {{ user.downloadedSize|octet_size(3) }}
Ratio: {{ ratio(user.uploadedSize, user.downloadedSize) }} (required {{ user.requiredRatio|number_format(2) }})
Torrent clients: {{ user.clients|join('; ') }}</div>
Torrent clients:
{% for u in user.stats.historyUseragentTracker %}
{{ u.useragent }} ({{ u.total }})
{% else %}
None
{% endfor %}
</div>
{% endif %}
<div class="box box_info box_userinfo_stats">
<div class="head colhead_dark">Statistics</div>

View File

@@ -76,7 +76,15 @@
{% endif %}
{%- if own_profile or viewer.permitted('users_mod') or viewer.isFLS %}
<li{% if not own_profile %} class="paranoia_override"{% endif %} id="torrent-client">Torrent clients: {{ user.clients|join('; ') }}</li>
<li{% if not own_profile %} class="paranoia_override"{% endif %} id="torrent-client">Torrent clients:
{% for u in user.stats.historyUseragentTracker %}
{{ u.useragent -}}
{% if viewer.permitted('users_mod') or (own_profile and user.hasAttr('seedbox-viewer')) %} ({{ u.total|number_format }}){% endif %}
{% if not loop.last %}; {% endif %}
{% else %}
None
{% endfor -%}
</li>
<li{% if not own_profile %} class="paranoia_override"{% endif %}>Password age: {{ user.history.passwordAge|time_interval }}</li>
{% endif %}

View File

@@ -237,13 +237,17 @@ class Helper {
\Gazelle\User $user,
string $ipAddr = '127.0.0.1',
int $interval = 5,
string|null $useragent = null,
): int {
if (is_null($useragent)) {
$useragent = 'ua-' . randomString(12);
}
$db = \Gazelle\DB::DB();
$db->prepared_query("
INSERT INTO xbt_files_users
(fid, uid, useragent, peer_id, ip, active, remaining, timespent, mtime)
VALUES (?, ?, ?, ?, ?, 1, 0, 1, unix_timestamp(now() - interval ? second))
", $torrent->id, $user->id, 'ua-' . randomString(12), randomString(20), $ipAddr, $interval
(fid, uid, useragent, peer_id, ip, mtime, active, remaining, timespent)
VALUES (?, ?, ?, ?, ?, unix_timestamp(now() - interval ? second), 1, 0, 1)
", $torrent->id, $user->id, $useragent, randomString(20), $ipAddr, $interval
);
return $db->affected_rows();
}

View File

@@ -162,7 +162,24 @@ class TorrentTest extends TestCase {
// a seeder
$this->userList['seeder'] = Helper::makeUser('torrent.' . randomString(10), 'rent', clearInbox: true);
Helper::generateTorrentSeed($torrent, $this->userList['seeder']);
$useragent = 'uahist-' . randomString(10);
Helper::generateTorrentSeed(
$torrent,
$this->userList['seeder'],
ipAddr: '127.1.1.1',
useragent: $useragent
);
// This is as good a place as any to test this
new Stats\Users()->refreshUseragentTracker();
$this->assertEquals(
[[
"useragent" => $useragent,
"total" => 1,
]],
$this->userList['seeder']->stats()->historyUseragentTracker(),
'user-status-history-useragent-tracker',
);
$name = $torrent->fullName();
$path = $torrent->path();