add audio/total file counts display

This commit is contained in:
Spine
2024-11-10 05:14:26 +00:00
parent a2952611d9
commit 2969363802
31 changed files with 414 additions and 94 deletions

View File

@@ -256,18 +256,18 @@ abstract class TorrentAbstract extends BaseObject {
}
/**
* Aggregate the audio files per audio type
* Aggregate the primary media file types per extension
*
* @return array of array of [ac3, flac, m4a, mp3] => count
* @return array of array of [ac3, flac, m4a, mp3, ...] => count
*/
public function fileListAudioMap(): array {
public function fileListPrimaryMap(): array {
$map = [];
foreach ($this->fileList() as $file) {
if (is_null($file['ext'])) {
continue;
}
$ext = substr($file['ext'], 1); // skip over period
if (in_array($ext, ['ac3', 'flac', 'm4a', 'mp3'])) {
if (preg_match('/^' . PRIMARY_EXT_REGEXP . '$/', $ext)) {
if (!isset($map[$ext])) {
$map[$ext] = 0;
}
@@ -277,6 +277,23 @@ abstract class TorrentAbstract extends BaseObject {
return $map;
}
public function fileListPrimaryTotal(): int {
return (int)array_reduce(
array_values($this->fileListPrimaryMap()),
fn ($total, $n) => $total += $n
);
}
public function fileListNonPrimarySize(): int {
$size = 0;
foreach ($this->fileList() as $file) {
if (!preg_match('/^\.' . PRIMARY_EXT_REGEXP . '$/', (string)$file['ext'])) {
$size += (int)$file['size'];
}
}
return $size;
}
/**
* Create a string that contains file info in the old format for the API
*

View File

@@ -43,6 +43,7 @@ class User extends BaseObject {
sprintf('users_tokens_%d', $this->id),
]);
$this->stats()->flush();
$this->ordinal()->flush();
$this->privilege()->flush();
unset($this->info, $this->ordinal, $this->privilege, $this->stats, $this->tokenCache);
return $this;

View File

@@ -2,6 +2,15 @@
namespace Gazelle\User;
/**
* Note: there is no userHasItem() method to check if a user has bought a
* particular item in the shop. Due to the wide disparity between items that can
* be bought only once (such as the seedbox viewer) and items that can be bought
* tens of thousands of times, adding an index to the (user, item) tuple would
* be enormously wasteful. Instead, a user attribute is used (whose index
* replies very efficiently to a yes/no has user purchased item question).
*/
class Bonus extends \Gazelle\BaseUser {
final public const tableName = 'bonus_history';
final protected const CACHE_PURCHASE = 'bonus_purchase_%d';
@@ -35,6 +44,8 @@ class Bonus extends \Gazelle\BaseUser {
foreach ($items as $item) {
if ($item['Label'] === 'seedbox' && $this->user->hasAttr('feature-seedbox')) {
continue;
} elseif ($item['Label'] === 'file-count' && $this->user->hasAttr('feature-file-count')) {
continue;
} elseif (
$item['Label'] === 'invite'
&& ($this->user->permitted('site_send_unlimited_invites') || !$this->user->canPurchaseInvite())
@@ -314,6 +325,39 @@ class Bonus extends \Gazelle\BaseUser {
return true;
}
public function purchaseFeatureFilecount(): bool {
$item = $this->items()['file-count'];
$price = $this->effectivePrice('file-count');
self::$db->begin_transaction();
self::$db->prepared_query('
UPDATE user_bonus ub SET
ub.points = ub.points - ?
WHERE ub.points >= ?
AND ub.user_id = ?
', $price, $price, $this->id()
);
if (self::$db->affected_rows() != 1) {
self::$db->rollback();
return false;
}
try {
self::$db->prepared_query("
INSERT INTO user_has_attr
(UserID, UserAttrID)
VALUES (?, (SELECT ID FROM user_attr WHERE Name = ?))
", $this->id(), 'feature-file-count'
);
} catch (\Gazelle\DB\MysqlDuplicateKeyException) {
// no point in buying a second time
self::$db->rollback();
return false;
}
self::$db->commit();
$this->addPurchaseHistory($item['ID'], $price);
$this->flush();
return true;
}
public function purchaseToken(string $label): bool {
$item = $this->items()[$label];
if (!$item) {

View File

@@ -846,6 +846,13 @@ defined('OBSOLETE_ENDPOINTS') or define('OBSOLETE_ENDPOINTS', []);
// ------------------------------------------------------------------------
// Upload configuration
// The name of the primary media this tracker focuses on (audio, movie, comics, ...)
defined('PRIMARY_MEDIA') or define('PRIMARY_MEDIA', 'audio');
// Define the primary media extensions the site in interested in.
// This is used to indicate torrents containing large amounts of "uninteresting" content.
defined('PRIMARY_EXT_REGEXP') or define('PRIMARY_EXT_REGEXP', '(?:a(?:ac|c3)|d(?:[fs]f|ts)|flac|m(?:4a|p3))');
// Upload categories.
defined('CATEGORY') or define('CATEGORY', [
'Music',

View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class FileCountOrdinal extends AbstractMigration {
public function up(): void {
$this->table('user_ordinal')
->insert([
[
'name' => 'file-count-display',
'description' => 'Configure how filelist counts are displayed on torrent pages',
'default_value' => 0,
],
[
'name' => 'non-audio-threshold',
'description' => 'Configure torrent display when non-audio filesize totals exceed a threshold',
'default_value' => 0,
],
])
->save();
$this->table('user_attr')
->insert([
[
'Name' => 'feature-file-count',
'Description' => 'This user has purchased the file count display feature'
],
])
->save();
$this->table('bonus_item')
->insert([
[
'Price' => 12000,
'Amount' => 1,
'MinClass' => 150,
'FreeClass' => 999999,
'Label' => 'file-count',
'Title' => 'Configure file count display on the torrent detail page',
'sequence' => 20,
],
])
->save();
}
public function down(): void {
$this->query("
delete from user_ordinal where name in ('file-count-display', 'non-audio-threshold');
");
$this->query("
delete from user_attr where Name = 'feature-file-count';
");
$this->execute("
delete from bonus_item where Label = 'file-count'
");
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class AudioPrimary extends AbstractMigration {
public function up(): void {
$this->query("
update user_ordinal set name = 'non-primary-threshold' where name = 'non-audio-threshold';
");
}
public function down(): void {
$this->query("
update user_ordinal set name = 'non-audio-threshold' where name = 'non-primary-threshold';
");
}
}

View File

@@ -303,7 +303,8 @@ if ($sections = $Artist->sections()) {
<td class="m_th_left m_th_left_collapsable" width="70%"><a href="#">↑</a>&nbsp;<strong><?=
$artistMan->sectionTitle((int)$sectionId) ?></strong> <a href="#" class="tooltip brackets" onclick="$('.releases_<?=
$sectionId ?>').gtoggle(true); return false;" title="Show/hide this section">Toggle</a></td>
<td>Size</td>
<td class="number_column">Files</td>
<td class="number_column">Size</td>
<td class="sign snatches"><img src="<?= $urlStem ?>snatched.png" class="tooltip" alt="Snatches" title="Snatches" /></td>
<td class="sign seeders"><img src="<?= $urlStem ?>seeders.png" class="tooltip" alt="Seeders" title="Seeders" /></td>
<td class="sign leechers"><img src="<?= $urlStem ?>leechers.png" class="tooltip" alt="Leechers" title="Leechers" /></td>
@@ -320,7 +321,7 @@ if ($sections = $Artist->sections()) {
?>
<tr class="releases_<?= $sectionId ?> group groupid_<?= $groupId ?>_header discog<?= ($sectionClosed ? ' hidden' : '') . ($isSnatched ? ' snatched_group' : '') ?>">
<?= $Twig->render('tgroup/collapse-tgroup.twig', [ 'closed' => $groupsClosed, 'id' => $groupId ]) ?>
<td colspan="5" class="td_info big_info">
<td colspan="6" class="td_info big_info">
<?php if ($Viewer->option('CoverArt')) { ?>
<div class="group_image float_left clear">
<?= $imgProxy->tgroupThumbnail($tgroup) ?>

View File

@@ -24,7 +24,7 @@ switch ($_GET['action'] ?? '') {
}
require_once(match ($Label) {
'invite' => 'invite.php',
'collage-1', 'seedbox' => 'purchase.php',
'collage-1', 'seedbox', 'file-count' => 'purchase.php',
'title-bb-y', 'title-bb-n', 'title-off' => 'title.php',
'token-1', 'token-2', 'token-3', 'token-4' => 'tokens.php',
'other-1', 'other-2', 'other-3', 'other-4' => 'token_other.php',

View File

@@ -16,6 +16,11 @@ if ($label === 'collage-1') {
error('Could not unlock the seedbox viewer. Either you have already unlocked it, or you lack the required bonus points.');
}
header("Location: bonus.php?complete=$label");
} elseif ($label === 'file-count') {
if (!$bonus->purchaseFeatureFilecount()) {
error('Could not purchase the file count feature. Either you have already own it, or you lack the required bonus points.');
}
header("Location: bonus.php?complete=$label");
} else {
error(403);
}

View File

@@ -191,7 +191,8 @@ $urlStem = (new Gazelle\User\Stylesheet($Viewer))->imagePath();
<td><!-- expand/collapse --></td>
<td><!-- Category --></td>
<td class="m_th_left m_th_left_collapsable" width="70%"><strong>Torrents</strong></td>
<td>Size</td>
<td class="number_column nobr">Files</td>
<td class="number_column nobr">Size</td>
<td class="sign snatches"><img src="<?= $urlStem ?>snatched.png" class="tooltip" alt="Snatches" title="Snatches" /></td>
<td class="sign seeders"><img src="<?= $urlStem ?>seeders.png" class="tooltip" alt="Seeders" title="Seeders" /></td>
<td class="sign leechers"><img src="<?= $urlStem ?>leechers.png" class="tooltip" alt="Leechers" title="Leechers" /></td>
@@ -217,7 +218,7 @@ foreach ($bookmarkList as $bm) {
<td class="m_hidden center">
<div title="<?= $tgroup->primaryTag() ?>" class="tooltip <?= $tgroup->categoryCss() ?> <?= $tgroup->primaryTagCss() ?>"></div>
</td>
<td class="td_info" colspan="5">
<td class="td_info" colspan="6">
<strong><?= $tgroup->link() ?></strong>
<span style="text-align: right;" class="float_right">
<?php if ($ownProfile) { ?>
@@ -273,7 +274,7 @@ foreach ($bookmarkList as $bm) {
<span class="float_right float_clear"><?= time_diff($bm['added']); ?></span>
</td>
<?= $Twig->render('torrent/stats.twig', ['torrent' => $torrent]) ?>
<?= $Twig->render('torrent/stats.twig', ['torrent' => $torrent, 'user' => $Viewer]) ?>
</tr>
<?php
}

View File

@@ -176,7 +176,8 @@ echo ' selected="selected"'; } ?>>Prefer Bonus Tracks</option>
<td><!-- expand/collapse --></td>
<td><!-- Category --></td>
<td class="m_th_left" width="70%"><strong>Torrents</strong></td>
<td>Size</td>
<td class="number_column nobr">Files</td>
<td class="number_column nobr">Size</td>
<td class="sign snatches"><img src="<?= $urlStem ?>snatched.png" class="tooltip" alt="Snatches" title="Snatches" /></td>
<td class="sign seeders"><img src="<?= $urlStem ?>seeders.png" class="tooltip" alt="Seeders" title="Seeders" /></td>
<td class="sign leechers"><img src="<?= $urlStem ?>leechers.png" class="tooltip" alt="Leechers" title="Leechers" /></td>
@@ -212,7 +213,7 @@ foreach ($entryList as $tgroupId) {
<td class="center">
<div title="<?= $tgroup->primaryTag() ?>" class="tooltip <?= $tgroup->categoryCss() ?> <?= $tgroup->primaryTagCss() ?>"></div>
</td>
<td colspan="5">
<td colspan="6">
<strong><?= $Number ?> <?= $tgroup->link() ?></strong>
<span class="float_right">
<?php
@@ -242,7 +243,7 @@ foreach ($entryList as $tgroupId) {
$EditionID++;
?>
<tr class="group_torrent groupid_<?= $tgroupId ?> edition<?= $SnatchedGroupClass . ($groupsClosed ? ' hidden' : '') ?>">
<td colspan="7" class="edition_info">
<td colspan="8" class="edition_info">
<?= $Twig->render('torrent/edition-header.twig', [
'edition_id' => $EditionID,
'tgroup' => $tgroup,
@@ -285,7 +286,7 @@ foreach ($entryList as $tgroupId) {
</div>
<?php } ?>
</td>
<?= $Twig->render('torrent/stats.twig', ['torrent' => $torrent]) ?>
<?= $Twig->render('torrent/stats.twig', ['torrent' => $torrent, 'user' => $Viewer]) ?>
</tr>
<?php
}

View File

@@ -140,7 +140,7 @@ if ($search->canUnclaim($Viewer)) {
<br />Last action: <?= $extra->lastActiveDate() ?: 'Never' ?>
<br /><span>Audio files present:
<?php
$extMap = $extra->fileListAudioMap();
$extMap = $extra->fileListPrimaryMap();
if (count($extMap) == 0) {
?>
<span class="file_ext_none">none</span>

View File

@@ -186,11 +186,12 @@ foreach ($context as $c) {
<td class="center" style="width: 15px;"></td>
<td class="cats_col"></td>
<td class="m_th_left m_th_left_collapsable">Name</td>
<td style="text-align: right;">Size</td>
<td style="text-align: right;" class="sign snatches"><img src="<?= $urlStem ?>snatched.png" alt="Snatches" title="Snatches" class="tooltip" /></td>
<td style="text-align: right;" class="sign seeders"><img src="<?= $urlStem ?>seeders.png" alt="Seeders" title="Seeders" class="tooltip" /></td>
<td style="text-align: right;" class="sign leechers"><img src="<?= $urlStem ?>leechers.png" alt="Leechers" title="Leechers" class="tooltip" /></td>
<td style="text-align: right;">Transferred</td>
<td class="number_column nobr">Files</td>
<td class="number_column nobr">Size</td>
<td class="sign snatches"><img src="<?= $urlStem ?>snatched.png" alt="Snatches" title="Snatches" class="tooltip" /></td>
<td class="sign seeders"><img src="<?= $urlStem ?>seeders.png" alt="Seeders" title="Seeders" class="tooltip" /></td>
<td class="sign leechers"><img src="<?= $urlStem ?>leechers.png" alt="Leechers" title="Leechers" class="tooltip" /></td>
<td class="number_column nobr">Transferred</td>
</tr>
<?php if (!$details) { ?>
<tr class="rowb">
@@ -242,7 +243,7 @@ foreach ($context as $c) {
<div class="tags"><?= implode(', ', $tgroup->tagNameList()) ?></div>
</div>
</td>
<?= $Twig->render('torrent/stats.twig', ['torrent' => $torrent]) ?>
<?= $Twig->render('torrent/stats.twig', ['torrent' => $torrent, 'user' => $Viewer]) ?>
<td class="td_data number_column nobr"><?= byte_format($data) ?></td>
</tr>
<?php } ?>

View File

@@ -115,7 +115,8 @@ echo display_str($_GET['year2']);} ?>" />
<td><!-- expand/collapse --></td>
<td class="cats_col"><!-- category --></td>
<td class="m_th_left" width="70%">Torrents</td>
<td>Size</td>
<td class="number_column nobr">Files</td>
<td class="number_column nobr">Size</td>
<td class="sign snatches"><img src="<?= $urlStem ?>snatched.png" alt="Snatches" title="Snatches" class="tooltip" /></td>
<td class="sign seeders"><img src="<?= $urlStem ?>seeders.png" alt="Seeders" title="Seeders" class="tooltip" /></td>
<td class="sign leechers"><img src="<?= $urlStem ?>leechers.png" alt="Leechers" title="Leechers" class="tooltip" /></td>
@@ -173,7 +174,7 @@ echo ' [' . $tgroup->year() . ']'; } ?></strong>
)) ?></div>
</div>
</td>
<td colspan="4" class="votes_info_td">
<td colspan="5" class="votes_info_td">
<?= $Twig->render('bookmark/action.twig', [
'class' => 'torrent',
'id' => $tgroupId,
@@ -231,7 +232,7 @@ echo ' [' . $tgroup->year() . ']'; } ?></strong>
fn($name) => "<a href=\"collages.php?action=search&tags=$name\">$name</a>", $tgroup->tagNameList()
)) ?></div>
</td>
<?= $Twig->render('torrent/stats.twig', ['torrent' => $torrent]) ?>
<?= $Twig->render('torrent/stats.twig', ['torrent' => $torrent, 'user' => $Viewer]) ?>
</tr>
<?php
}

View File

@@ -140,9 +140,9 @@ echo $paginator->linkbox();
<?php } ?>
<td class="small cats_col"></td>
<td class="m_th_left m_th_left_collapsable nobr" width="100%">Name / <?= $header->emit('year') ?></td>
<td>Files</td>
<td class="nobr"><?= $header->emit('time') ?></td>
<td class="nobr"><?= $header->emit('size') ?></td>
<td class="number_column">Files</td>
<td class="number_column nobr"><?= $header->emit('size') ?></td>
<td class="sign nobr snatches"><?= $headerIcons->emit('snatched') ?></td>
<td class="sign nobr seeders"><?= $headerIcons->emit('seeders') ?></td>
<td class="sign nobr leechers"><?= $headerIcons->emit('leechers') ?></td>
@@ -195,17 +195,18 @@ foreach ($Results as $Key => $GroupID) {
) ?></div>
</div>
</td>
<td></td>
<td class="td_time nobr"><?=time_diff($tgroup->mostRecentUpload(), 1)?></td>
<td></td>
<td class="td_size number_column nobr"><?= byte_format($tgroup->maxTorrentSize()) ?> (Max)</td>
<td class="td_snatched number_column m_td_right"><?=number_format($tgroup->stats()->snatchTotal())?></td>
<td class="td_seeders number_column<?= $tgroup->stats()->seedingTotal() == 0 ? ' r00' : '' ?> m_td_right"><?=number_format($tgroup->stats()->seedingTotal())?></td>
<td class="td_leechers number_column m_td_right"><?=number_format($tgroup->stats()->leechTotal())?></td>
</tr>
<?php
$prev = '';
$EditionID = 0;
$prev = '';
$EditionID = 0;
$UnknownCounter = 0;
$prevPrimaryTotal = null;
foreach ($torrentList as $torrentId) {
$torrent = $torMan->findById($torrentId);
@@ -245,11 +246,15 @@ foreach ($Results as $Key => $GroupID) {
]) ?>
&raquo; <?= $torrent->shortLabelLink() ?>
</td>
<td class="td_file_count"><?=$torrent->fileTotal()?></td>
<td class="td_time nobr"><?=time_diff($torrent->created(), 1)?></td>
<?= $Twig->render('torrent/stats.twig', ['torrent' => $torrent]) ?>
<?= $Twig->render('torrent/stats.twig', [
'prev_primary' => $prevPrimaryTotal,
'torrent' => $torrent,
'user' => $Viewer
]) ?>
</tr>
<?php
$prevPrimaryTotal = $torrent->fileListPrimaryTotal();
}
} else {
// Viewing a type that does not require grouping
@@ -285,9 +290,8 @@ foreach ($Results as $Key => $GroupID) {
) ?></div>
</div>
</td>
<td class="td_file_count"><?= $torrent->fileTotal() ?></td>
<td class="td_time nobr"><?=time_diff($torrent->created(), 1)?></td>
<?= $Twig->render('torrent/stats.twig', ['torrent' => $torrent]) ?>
<?= $Twig->render('torrent/stats.twig', ['torrent' => $torrent, 'user' => $Viewer]) ?>
</tr>
<?php
}

View File

@@ -158,7 +158,8 @@ echo $Twig->render('collage/summary.twig', [
<table class="torrent_table details<?= $tgroup->isSnatched() ? ' snatched' : ''?> m_table" id="torrent_details">
<tr class="colhead_dark">
<td class="m_th_left" width="80%"><strong>Torrents</strong></td>
<td><strong>Size</strong></td>
<td class="number_column"><strong>Files</strong></td>
<td class="number_column"><strong>Size</strong></td>
<td class="m_th_right sign snatches"><img src="<?= $urlStem ?>snatched.png" class="tooltip" alt="Snatches" title="Snatches" /></td>
<td class="m_th_right sign seeders"><img src="<?= $urlStem ?>seeders.png" class="tooltip" alt="Seeders" title="Seeders" /></td>
<td class="m_th_right sign leechers"><img src="<?= $urlStem ?>leechers.png" class="tooltip" alt="Leechers" title="Leechers" /></td>
@@ -170,7 +171,7 @@ if (!$torrentList) {
$mastering = implode('/', [$info['year'], $info['title'], $info['record_label'], $info['catalogue_number'], $info['media']]);
?>
<tr class="releases_<?= $tgroup->releaseType() ?> groupid_<?=$tgroupId?> edition group_torrent">
<td colspan="5" class="edition_info"><strong>[<?= html_escape($mastering) ?>]</strong></td>
<td colspan="6" class="edition_info"><strong>[<?= html_escape($mastering) ?>]</strong></td>
</tr>
<tr>
<td><i>deleted</i></td>

View File

@@ -25,7 +25,7 @@ $imgTag = '<img loading="lazy" src="' . (new Gazelle\User\Stylesheet($Viewer))->
. '%s.png" class="tooltip" alt="%s" title="%s"/>';
$headerMap = [
'year' => ['dbColumn' => 'tg.Year', 'defaultSort' => 'desc', 'text' => 'Year'],
'time' => ['dbColumn' => 'unt.TorrentID', 'defaultSort' => 'desc', 'text' => 'Time'],
'time' => ['dbColumn' => 'unt.TorrentID', 'defaultSort' => 'desc', 'text' => 'Created'],
'size' => ['dbColumn' => 't.Size', 'defaultSort' => 'desc', 'text' => 'Size'],
'snatched' => ['dbColumn' => 'tls.Snatched', 'defaultSort' => 'desc', 'text' => sprintf($imgTag, 'snatched', 'Snatches', 'Snatches')],
'seeders' => ['dbColumn' => 'tls.Seeders', 'defaultSort' => 'desc', 'text' => sprintf($imgTag, 'seeders', 'Seeders', 'Seeders')],
@@ -99,9 +99,9 @@ View::show_header(($ownProfile ? 'My' : $user->username() . "'s") . ' notificati
<td style="text-align: center;"><input type="checkbox" name="toggle" onclick="toggleChecks('notificationform_<?=$filterId?>', this, '.notify_box')" /></td>
<td class="small cats_col"></td>
<td style="width: 100%;" class="nobr">Name<?= ' / ' . $header->emit('year') ?></td>
<td>Files</td>
<td class="nobr"><?= $header->emit('time') ?></td>
<td class="nobr"><?= $header->emit('size') ?></td>
<td class="number_column">Files</td>
<td class="nobr number_column"><?= $header->emit('size') ?></td>
<td class="sign nobr snatches"><?= $headerIcons->emit('snatched') ?></td>
<td class="sign nobr seeders"><?= $headerIcons->emit('seeders') ?></td>
<td class="sign nobr leechers"><?= $headerIcons->emit('leechers') ?></td>
@@ -158,9 +158,8 @@ View::show_header(($ownProfile ? 'My' : $user->username() . "'s") . ' notificati
<?= display_str($match ? 'Caught by filter for ' . implode(', ', $match) : '') ?>
</div>
</td>
<td class="td_file_count"><?= $torrent->fileTotal() ?></td>
<td class="td_time nobr"><?= time_diff($torrent->created(), 1) ?></td>
<?= $Twig->render('torrent/stats.twig', ['torrent' => $torrent]) ?>
<?= $Twig->render('torrent/stats.twig', ['torrent' => $torrent, 'user' => $Viewer]) ?>
</tr>
<?php } ?>
</table>

View File

@@ -23,7 +23,7 @@ $imgTag = '<img loading="lazy" src="' . (new Gazelle\User\Stylesheet($Viewer))->
. '%s.png" class="tooltip" alt="%s" title="%s"/>';
$headerMap = [
'name' => ['dbColumn' => 'tg.Name', 'defaultSort' => 'asc', 'text' => 'Torrent'],
'time' => ['dbColumn' => 'Time', 'defaultSort' => 'desc', 'text' => 'Time'],
'time' => ['dbColumn' => 'Time', 'defaultSort' => 'desc', 'text' => 'Created'],
'size' => ['dbColumn' => 't.Size', 'defaultSort' => 'desc', 'text' => 'Size'],
'snatched' => [
'dbColumn' => 'tls.Snatched',
@@ -482,7 +482,8 @@ foreach (CATEGORY as $catKey => $catName) {
<td class="cats_col"></td>
<td class="m_th_left nobr"><?= $header->emit('name') ?></td>
<td class="nobr"><?= $header->emit('time') ?></td>
<td class="nobr"><?= $header->emit('size') ?></td>
<td class="number_column nobr">Files</td>
<td class="number_column nobr"><?= $header->emit('size') ?></td>
<td class="sign nobr snatches m_th_right"><?= $headerIcons->emit('snatched') ?></td>
<td class="sign nobr seeders m_th_right"><?= $headerIcons->emit('seeders') ?></td>
<td class="sign nobr leechers m_th_right"><?= $headerIcons->emit('leechers') ?></td>
@@ -528,7 +529,7 @@ foreach (CATEGORY as $catKey => $catName) {
</div>
</td>
<td class="td_time nobr"><?=time_diff($info['Time'], 1)?></td>
<?= $Twig->render('torrent/stats.twig', ['torrent' => $torrent]) ?>
<?= $Twig->render('torrent/stats.twig', ['torrent' => $torrent, 'user' => $Viewer]) ?>
</tr>
<?php
} /* foreach */ ?>

View File

@@ -280,6 +280,11 @@ if (isset($_POST['resetpasskey'])) {
$user->modify();
$ordinal = $user->ordinal();
if ($user->hasAttr('feature-file-count') || $Viewer->isStaff()) {
$ordinal->set('file-count-display', (int)$_POST['file-count-display']);
$ordinal->set('non-primary-threshold', (int)$_POST['non-primary-threshold']);
}
$requestBountyCreate = max(
REQUEST_MIN * 1024 * 1024, // never go below request minimum
min(

View File

@@ -151,7 +151,7 @@ View::show_header('Subscribed collages', ['js' => 'browse,collage']);
) ?></div>
</div>
</td>
<?= $Twig->render('torrent/stats.twig', ['torrent' => $torrent]) ?>
<?= $Twig->render('torrent/stats.twig', ['torrent' => $torrent, 'user' => $Viewer]) ?>
</tr>
<?php
}

View File

@@ -1,14 +1,14 @@
{% for item in list %}
{% if loop.first %}
{% if loop.first %}
<div class="thin">
{% if discount %}
{% if discount %}
<h3 style="text-align: center; color: lime;">All prices currently {{ min(100, max(0, discount)) }}% off &mdash; Hurry, sale ends soon &mdash; While stocks last!</h3>
{% endif %}
{% if admin %}
{% endif %}
{% if admin %}
<div class="pad box">
<div class="thin">NB: Bonus Shop discounts are set in the <a href="/tools.php?action=site_options">Site Options</a>.</div>
</div>
{% endif %}
{% endif %}
<table>
<thead>
<tr class="colhead">
@@ -18,27 +18,27 @@
</tr>
</thead>
<tbody>
{% endif %}
{% if item.MinClass <= class %}
{% endif %}
{% if item.MinClass <= class %}
<tr class="row{{ cycle(['a', 'b'], loop.index0) }}">
<td>{{ item.Title }}</td>
<td>{{ item.Title }}</pre></td>
<td style="text-align:right">{{ item.Price|number_format }}</td>
<td>
{% if points >= item.Price %}
{% if points >= item.Price %}
<a id="bonusconfirm" href="bonus.php?action=purchase&amp;label={{ item.Label
}}&amp;auth={{ auth }}" onclick="{{ item.JS_on_click }}(event, '{{ item.Title }}', {{ item.JS_next_function }}, this);">Purchase</a>
{% else %}
}}&amp;auth={{ auth }}" onclick="{{ item.JS_on_click }}(event, '{{ item.Title }}', {{ item.JS_next_function|default('null') }}, this);">Purchase</a>
{% else %}
<span style="font-style: italic">Too Expensive</span>
{% endif %}
{% endif %}
{% endif %}
{% endif %}
</td>
</tr>
{% if loop.last %}
{% if loop.last %}
</tbody>
</table>
<br />
</div>
{% endif %}
{% endif %}
{% endfor %}
{{ footer() }}

View File

@@ -10,10 +10,11 @@
<table class="torrent_table details{% if viewer.snatch.showSnatch(torrent) %} snatched{% endif %}" id="torrent_details">
<tr class="colhead_dark">
<td width="80%"><strong>Reported torrent</strong></td>
<td><strong>Size</strong></td>
<td class="sign snatches"><img src="{{ urlStem }}snatched.png" class="tooltip" alt="Snatches" title="Snatches" /></td>
<td class="sign seeders"><img src="{{ urlStem }}seeders.png" class="tooltip" alt="Seeders" title="Seeders" /></td>
<td class="sign leechers"><img src="{{ urlStem }}leechers.png" class="tooltip" alt="Leechers" title="Leechers" /></td>
<td class="number_column nobr"><strong>Files</strong></td>
<td class="number_column nobr"><strong>Size</strong></td>
<td class="sign snatches"><img src="{{ url_stem }}snatched.png" class="tooltip" alt="Snatches" title="Snatches" /></td>
<td class="sign seeders"><img src="{{ url_stem }}seeders.png" class="tooltip" alt="Seeders" title="Seeders" /></td>
<td class="sign leechers"><img src="{{ url_stem }}leechers.png" class="tooltip" alt="Leechers" title="Leechers" /></td>
</tr>
{% include 'torrent/detail-torrentgroup.twig' with {
'report_man' : report_man,

View File

@@ -45,7 +45,7 @@
<br />uploaded by {{ torrent.uploaderId|user_url }} on <span title="{{ torrent.created|time_diff(3, false) }}">{{ torrent.created }}</span>
<br />Last action: {{ torrent.lastActiveDate|default('Never') }}
<br /><span class="report_torrent_file_ext">Audio files present:
{% for ext, total in torrent.fileListAudioMap -%}
{% for ext, total in torrent.fileListPrimaryMap -%}
{%- if loop.first %}<span class="file_ext_map">{% else %}, {% endif -%}
{{ ext }}: {{ total|number_format }}
{%- if loop.last %}</span>{% endif -%}

View File

@@ -174,7 +174,7 @@
<td class="label">Order by:</td>
<td colspan="3" class="ft_order">
<select name="order" style="width: auto;" class="ft_order_by">
<option value="time"{{ selected(input.order == 'time') }}>Time added</option>
<option value="time"{{ selected(input.order == 'time') }}>Created</option>
<option value="year"{{ selected(input.order == 'year') }}>Year</option>
<option value="size"{{ selected(input.order == 'size') }}>Size</option>
<option value="snatched"{{ selected(input.order == 'snatched') }}>Snatched</option>

View File

@@ -14,18 +14,24 @@
'viewer': viewer
} only
%}
{% if show_extended %}
<a href="#" onclick="$('#torrent_{{ torrent_id }}').gtoggle(); return false;">{{ torrent.label(viewer)|raw }}</a>
{% else %}
<a href="{{ torrent.location }}">{{ torrent.label(viewer)|raw }}</a>
{% endif %}
<a href=
{%- if show_extended -%}
"#" onclick="$('#torrent_{{ torrent_id }}').gtoggle(); return false;">
{%- else -%}
"{{ torrent.location }}">
{%- endif -%}
{{ torrent.label(viewer)|raw }}</a>
</td>
{% include 'torrent/stats.twig' with {'torrent': torrent} %}
{% include 'torrent/stats.twig' with {
'prev_primary' : prev_primary,
'torrent' : torrent,
'user' : viewer,
} only
%}
</tr>
{% if show_extended %}
<tr id="torrent_{{ torrent_id }}" class="releases_{{ tgroup.releaseType }} groupid_{{ tgroup.id }} edition_{{ edition_id }} torrentdetails pad{{ hidden(torrent_id != show_id) }}">
<td colspan="{{ 5 + colspan_add }}">
<td colspan="{{ 6 + colspan_add }}">
<div id="release_{{ torrent_id }}" class="no_overflow">
<blockquote>
{% if folder_clash|length > 1 %}

View File

@@ -1,26 +1,28 @@
{% set prev = null %}
{% set edition_id = 0 %}
{% for torrent in torrent_list %}
{% set edition = torrent.remasterTuple %}
{% if tgroup.categoryGrouped and edition != prev %}
{% set edition_id = edition_id + 1 %}
{% set edition = torrent.remasterTuple %}
{% if tgroup.categoryGrouped and edition != prev %}
{% set edition_id = edition_id + 1 %}
{% set prev_primary = null %}
<tr class="releases_{{ tgroup.releaseType }} groupid_{{ tgroup.id }} edition group_torrent
{%- if is_snatched_grp %} snatched_group{% endif %}
{%- if hide %} hidden{% endif %}">
<td colspan="{{ 5 + colspan_add }}" class="edition_info">
{% include 'torrent/edition-header.twig' with {
'edition_id': edition_id,
'torrent' : torrent,
'tgroup' : tgroup,
} only %}
{% if is_snatched_grp %} snatched_group{% endif %}
{% if hide %} hidden{% endif %}">
<td colspan="{{ 6 + colspan_add }}" class="edition_info">
{% include 'torrent/edition-header.twig' with {
'edition_id': edition_id,
'torrent' : torrent,
'tgroup' : tgroup,
} only %}
</td>
</tr>
{% endif %}
{% include 'torrent/detail-torrent.twig' with {
{% endif %}
{% include 'torrent/detail-torrent.twig' with {
'colspan_add' : colspan_add,
'edition_id' : edition_id,
'folder_clash' : torMan.findAllByFoldername(torrent.path),
'hide' : hide,
'prev_primary' : prev_primary,
'report_man' : report_man,
'show_extended': show_extended,
'show_id' : show_id,
@@ -28,5 +30,6 @@
'torrent' : torrent,
'viewer' : viewer,
} only %}
{% set prev = edition %}
{% set prev = edition %}
{% set prev_primary = torrent.fileListPrimaryTotal %}
{% endfor %}

View File

@@ -1,4 +1,4 @@
<strong><a href="#" onclick="toggle_edition({{ tgroup.id }}, {{ edition_id }}, this, event);"
title="Collapse this edition. Hold [Ctrl/Command] while clicking to collapse all editions in this torrent group."
class="tooltip edition_toggle"></a>
{{- torrent.edition }}</strong>
{{- torrent.edition }}</strong>

View File

@@ -1,4 +1,46 @@
<td class="number_column td_size nobr">{{ torrent.size|octet_size }}</td>
{% if user.hasAttr('feature-file-count') %}
{% set display_mode = user.ordinal.value('file-count-display') %}
{% set warn_open = '' %}
{% set warn_close = '' %}
{% if display_mode > 0 %}
{% set primary_total = torrent.fileListPrimaryTotal %}
{% if display_mode == 1 %}
{% set display = torrent.fileTotal|number_format %}
{% elseif display_mode == 2 %}
{% set display %}{{ primary_total|number_format }}/{{
torrent.fileTotal|number_format }}{% endset %}
{% elseif display_mode == 3 %}
{% set display %}{{ primary_total|number_format }}+{{
(torrent.fileTotal - primary_total)|number_format }}{% endset %}
{% elseif display_mode == 4 %}
{% set display = primary_total|number_format %}
{% endif %}
{% if prev_primary is not null and prev_primary != primary_total %}
{% set warn_open %}
<strong class="important_text tooltip" title="{{
constant('PRIMARY_MEDIA')|ucfirst }} file counts differ ({{
prev_primary }}{{ primary_total }})">
{% endset %}
{% set warn_close = '</strong>' %}
{% endif %}
{% else %}
{% set display = torrent.fileTotal|number_format %}
{% endif %}
{% if user.ordinal.value('non-primary-threshold') > 0
and user.ordinal.value('non-primary-threshold') * 1024 * 1024
<= torrent.fileListNonPrimarySize
%}
{% set non_primary = torrent.fileListNonPrimarySize %}
{% else %}
{% set non_primary = 0 %}
{% endif %}
{% else %}
{% set display = torrent.fileTotal|number_format %}
{% endif %}
<td class="number_column td_filecount nobr">{{ warn_open|raw }}{{ display }}{{ warn_close|raw }}</td>
<td class="number_column td_size nobr"
{% if non_primary %} style="background-color: #f5b041" title="Extra: {{ non_primary|octet_size }}"{% endif %}
>{{ torrent.size|octet_size }}</td>
<td class="number_column m_td_right td_snatched">{{ torrent.snatchTotal|number_format }}</td>
<td class="number_column m_td_right td_seeders{% if torrent.seederTotal == 0 %} r00{% endif %}">{{ torrent.seederTotal|number_format }}</td>
<td class="number_column m_td_right td_leechers">{{ torrent.leecherTotal|number_format }}</td>

View File

@@ -159,10 +159,44 @@
<strong>Use HTTPS Tracker</strong>
</td>
<td>
<input type="checkbox" name="httpstracker" id="httpstracker"{{ checked(user.option('HttpsTracker')) }} />
<label for="httpstracker">Enable HTTPs tracker for announce URL</label>
<label><input type="checkbox" name="httpstracker" id="httpstracker"{{ checked(user.option('HttpsTracker')) }} />
Enable HTTPs tracker for announce URL</label>
</td>
</tr>
{% if user.hasAttr('feature-file-count') %}
<tr id="filecount_tr">
<td class="label tooltip" title="Show file counts in torrent listings">
<strong>File count display</strong>
</td>
<td>
<label><input type="radio" name="file-count-display" value="0"{{
checked(user.ordinal.value('file-count-display') == 0) }} />
None</label><br />
<label><input type="radio" name="file-count-display" value="1"{{
checked(user.ordinal.value('file-count-display') == 1) }} />
Total files</label><br />
<label><input type="radio" name="file-count-display" value="2"{{
checked(user.ordinal.value('file-count-display') == 2) }} />
{{ constant('PRIMARY_MEDIA')|ucfirst }} files / Total files</label><br />
<label><input type="radio" name="file-count-display" value="3"{{
checked(user.ordinal.value('file-count-display') == 3) }} />
{{ constant('PRIMARY_MEDIA')|ucfirst }} files + Non-{{
constant('PRIMARY_MEDIA') }} files</label><br />
<label><input type="radio" name="file-count-display" value="4"{{
checked(user.ordinal.value('file-count-display') == 4) }} />
{{ constant('PRIMARY_MEDIA')|ucfirst }} files only</label><br />
</td>
</tr>
<tr id="nonprimary_size_tr">
<td class="label tooltip" title="Highlight torrents with excessive non-{{ constant('PRIMARY_MEDIA') }} files">
<strong>Non-{{ constant('PRIMARY_MEDIA') }} threshold</strong>
</td>
<td>
<label><input type="input" size="4" name="non-primary-threshold" value="{{ user.ordinal.value('non-primary-threshold') }}" />
Highlight torrents with excessive non-{{ constant('PRIMARY_MEDIA') }} content (in MiB)</label>
</td>
</tr>
{% endif %}
<tr id="req-bounty-create">
<td class="label tooltip" title="This is the default amount of bounty you offer when creating requests">
<strong>Request creation</strong>

View File

@@ -76,8 +76,18 @@ class BonusTest extends TestCase {
$other = $giver->otherList();
$this->assertEquals('other-1', $other[0]['Label'], 'item-all-I-can-give');
// buy file count feature
$giver->addPoints(
(float)($giver->item('file-count')['Price'])
);
$this->assertTrue($giver->purchaseFeatureFilecount(), 'item-purchase-file-count');
$this->assertTrue($giver->user()->hasAttr('feature-seedbox'), 'giver-has-file-count');
$this->assertEquals(
$giver->item('token-1')['Price'] + $giver->item('other-3')['Price'] + $giver->item('seedbox')['Price'],
$giver->item('token-1')['Price']
+ $giver->item('other-3')['Price']
+ $giver->item('seedbox')['Price']
+ $giver->item('file-count')['Price'],
$giver->pointsSpent(),
'bonus-points-spent'
);
@@ -97,14 +107,15 @@ class BonusTest extends TestCase {
$this->assertTrue($giver->purchaseCollage('collage-1'), 'item-purchase-collage');
$history = $giver->history(10, 0);
$this->assertCount(6, $history, 'bonus-history-final');
$this->assertCount(7, $history, 'bonus-history-final');
$this->assertEquals(
[
'nr' => 6,
'nr' => 7,
'total' => $giver->item('token-1')['Price']
+ $giver->item('other-3')['Price']
+ $giver->item('seedbox')['Price']
+ $giver->item('file-count')['Price']
+ $giver->item('collage-1')['Price']
+ $giver->item('title-bb-y')['Price']
+ $giver->item('title-bb-n')['Price']

View File

@@ -46,6 +46,66 @@ class TorrentTest extends TestCase {
$this->assertFalse($this->torrent->hasFlag(TorrentFlag::badFile), 'torrent-no-more--bad-file-flag');
}
public function testContents(): void {
$bencoder = new \OrpheusNET\BencodeTorrent\BencodeTorrent();
$bencoder->decodeFile(__DIR__ . '/../cypress/files/valid_torrent.torrent');
$info = $bencoder->getData();
$this->assertIsArray($info, 'torrent-file-data-array');
$torrentFiler = new File\Torrent();
$this->assertTrue(
$torrentFiler->put($bencoder->getEncode(), $this->torrent->id()),
'torrent-file-put'
);
$this->assertTrue(
$torrentFiler->exists($this->torrent->id()),
'torrent-file-exists'
);
['total_size' => $totalSize, 'files' => $fileList] = $bencoder->getFileList();
$torMan = new Manager\Torrent();
['path' => $filename, 'size' => $size] = $fileList[0];
$this->assertEquals(
".flac s2056192s 1 track1.flac ÷",
$torMan->metaFilename($filename, $size),
'torrent-file-meta-filename',
);
$dbFileList = [];
foreach ($fileList as ['path' => $filename, 'size' => $size]) {
$dbFileList[] = $torMan->metaFilename($filename, $size);
}
$this->torrent->setField('FileList', implode("\n", $dbFileList))
->modify();
$this->assertEquals(
['flac' => 2],
$this->torrent->fileListPrimaryMap(),
'torrent-file-primary-map'
);
$this->assertEquals(
2,
$this->torrent->fileListPrimaryTotal(),
'torrent-file-primary-total'
);
$this->assertEquals(
7228,
$this->torrent->fileListNonPrimarySize(),
'torrent-file-non-primary-size'
);
$this->assertEquals(
"1 track1.flac{{{2056192}}}|||2 track2.flac{{{6152192}}}|||Test Album.log{{{7228}}}",
$this->torrent->fileListLegacyAPI(),
'torrent-file-legacy-list'
);
$this->assertEquals(8215612, $totalSize, 'torrent-file-total-size');
$this->assertCount(3, $fileList, 'torrent-file-list');
$this->assertTrue(
$torrentFiler->remove($this->torrent->id()),
'torrent-file-remove'
);
}
public function testRemovalPm(): void {
$torrent = \GazelleUnitTest\Helper::makeTorrentMusic(
tgroup: $this->torrent->group(),