file storage classes are object based

This commit is contained in:
Spine
2025-07-15 02:38:23 +00:00
parent be151fab96
commit dd8ee6160e
30 changed files with 391 additions and 285 deletions

View File

@@ -133,9 +133,9 @@ RUN useradd -ms /bin/bash gazelle \
| sed -r 's/pcntl_(fork|signal|signal_dispatch|waitpid),//g' \
> /etc/php/${PHP_VER}/cli/conf.d/99-boris.ini \
&& echo "Generate file storage directories..." \
&& perl /var/www/bin/generate-storage-dirs /var/lib/gazelle/torrent 2 100 \
&& perl /var/www/bin/generate-storage-dirs /var/lib/gazelle/riplog 2 100 \
&& perl /var/www/bin/generate-storage-dirs /var/lib/gazelle/riploghtml 2 100 \
&& perl /var/www/bin/generate-storage-dirs /var/lib/gazelle/torrent 2 100 \
&& chown -R gazelle:gazelle /var/lib/gazelle /var/www
EXPOSE 80/tcp

View File

@@ -2,46 +2,47 @@
namespace Gazelle;
abstract class File extends Base {
/**
* Note that currently, PHP does not allow an abstract class to define a method
* signature as array|int and then be specialized in the child class as either
* one or the other. So everything is defined as mixed and we will see if this
* may be revisited in a future version of PHP.
* It might also not be the best example of inheritance, but is simplifies much
* of the implementation as it is.
*/
abstract class File extends BaseObject {
/**
* Path to stored file
*/
abstract public function path(mixed $id): string;
abstract public function path(): string;
/**
* Does the file exist?
*/
public function exists(mixed $id): bool {
return file_exists($this->path($id));
public function exists(): bool {
return file_exists($this->path());
}
/**
* Store a file on disk at the specified path.
* Store some data as a file on disk at the specified path.
*/
public function put(string $source, mixed $id): bool {
return file_put_contents($this->path($id), $source) !== false;
public function put(string $contents): bool {
return file_put_contents($this->path(), $contents) !== false;
}
/**
* Retrieve the contents of the stored file.
*/
public function get(mixed $id): string|false {
return file_get_contents($this->path($id));
public function get(): string|false {
return file_get_contents($this->path());
}
/**
* Retrieve the size of the stored file. Note that the size
* of a file that does not exist will be 0 (which may occur
* if something bad happened). Use the exists() method to
* distinguish between a true 0-byte file and missing file.
*/
public function size(): int {
return (int)filesize($this->path());
}
/**
* Remove the stored file.
*/
public function remove(mixed $id): bool {
return unlink($this->path($id));
public function remove(): int {
return (int)unlink($this->path());
}
}

View File

@@ -2,55 +2,74 @@
namespace Gazelle\File;
/* Note that there can be multiple rip logs per torrent, so there is one
* torrent id and one or more log ids. In the usual course of operations,
* an object is created with new File(<torrent-int>, <log-int>) which
* points to one log (among possibly multiple) belonging to a torrent.
*
* A variant construction is allowed: new File(<int>, '*') which means
* "all the log files". In this case, calling the remove() method removes
* *all* the logs. You can of course remove just one log file if the log
* id is specified in the constructor with new File(<int>, <int>).
*/
class RipLog extends \Gazelle\File {
/**
* Move an existing rip log to the file storage location.
* NOTE: This is a change in behaviour from the parent class,
* which is expecting the file contents.
*
* $source Path to the file, usually the result of a POST operation.
* $id The unique identifier [torrentId, logId] of the object
*/
public function put(string $source, mixed $id): bool {
return false !== move_uploaded_file($source, $this->path($id));
public function __construct(
public readonly int $id,
public readonly int|string $logId,
) {}
public function flush(): static {
return $this;
}
/**
* Remove one or more rip logs of a torrent
*
* $id The unique identifier [torrentId, logId] of the object
* If logId is null, all logs are taken into consideration
*/
public function remove(mixed $id): bool {
[$torrentId, $logId] = $id;
if (is_null($logId)) {
$logfiles = glob($this->path([$torrentId, '*']));
if ($logfiles === false) {
return false;
}
foreach ($logfiles as $path) {
if (preg_match('/(\d+)\.log/', $path, $match)) {
$logId = $match[1];
$this->remove([$torrentId, $logId]);
}
}
return true;
} else {
if ($this->exists($id)) {
return unlink($this->path($id));
}
return false;
}
public function location(): string {
return "";
}
public function link(): string {
return "";
}
/**
* Path of a rip log.
*/
public function path(mixed $id): string {
[$torrentId, $logId] = $id;
$key = strrev(sprintf('%04d', $torrentId));
return sprintf("%s/%02d/%02d/{$torrentId}_{$logId}.log",
public function path(): string {
$key = strrev(sprintf('%04d', $this->id));
return sprintf("%s/%02d/%02d/{$this->id}_{$this->logId}.log",
STORAGE_PATH_RIPLOG, substr($key, 0, 2), substr($key, 2, 2)
);
}
/**
* Move an existing rip log to the file storage location.
* NOTE: This is a change in behaviour from the parent class,
* which is expecting the file contents.
*/
public function put(string $source): bool {
return move_uploaded_file($source, $this->path()) !== false;
}
/**
* Remove one or more rip logs of a torrent
*/
public function remove(): int {
if ($this->exists()) {
return (int)unlink($this->path());
} elseif ($this->logId === '*') {
$fileList = glob($this->path());
if ($fileList === false) {
return 0;
}
foreach ($fileList as $path) {
if (preg_match('/(\d+)\.log/', $path, $match)) {
if (!new self($this->id, (int)$match[1])->remove()) {
return 0;
}
}
}
return 1;
}
return 0;
}
}

View File

@@ -2,44 +2,56 @@
namespace Gazelle\File;
// see the comment in File\RipLog
class RipLogHTML extends \Gazelle\File {
/**
* Remove one or more HTML-ized rip logs of a torrent
*
* @param mixed $id The unique identifier [torrentId, logId] of the object
* If logId is null, all logs are taken into consideration
*/
public function remove(mixed $id): bool {
[$torrentId, $logId] = $id;
if (is_null($logId)) {
$htmlfiles = glob($this->path([$torrentId, '*']));
if ($htmlfiles === false) {
return false;
}
foreach ($htmlfiles as $path) {
if (preg_match('/(\d+)\.log/', $path, $match)) {
$logId = $match[1];
$this->remove([$torrentId, $logId]);
}
}
return true;
} else {
$path = $this->path($id);
if (file_exists($path)) {
return unlink($path);
}
return false;
}
public function __construct(
public readonly int $id,
public readonly int|string $logId,
) {}
public function flush(): static {
return $this;
}
public function location(): string {
return "";
}
public function link(): string {
return "";
}
/**
* Path of a HTML-ized rip log.
*/
public function path(mixed $id): string {
[$torrentId, $logId] = $id;
$key = strrev(sprintf('%04d', $torrentId));
return sprintf("%s/%02d/%02d/{$torrentId}_{$logId}.html",
public function path(): string {
$key = strrev(sprintf('%04d', $this->id));
return sprintf("%s/%02d/%02d/{$this->id}_{$this->logId}.html",
STORAGE_PATH_RIPLOGHTML, substr($key, 0, 2), substr($key, 2, 2)
);
}
/**
* Remove one or more HTML-ized rip logs of a torrent
*/
public function remove(): int {
if ($this->exists()) {
return (int)unlink($this->path());
} elseif ($this->logId === '*') {
$fileList = glob($this->path());
if ($fileList === false) {
return 0;
}
foreach ($fileList as $path) {
if (preg_match('/(\d+)\.html/', $path, $match)) {
if (!new self($this->id, (int)$match[1])->remove()) {
return 0;
}
}
}
return 1;
}
return 0;
}
}

View File

@@ -3,13 +3,29 @@
namespace Gazelle\File;
class Torrent extends \Gazelle\File {
public function __construct(
public readonly int $id,
) {}
public function flush(): static {
return $this;
}
public function location(): string {
return "";
}
public function link(): string {
return "";
}
/**
* Path of a torrent file
*/
public function path(mixed $id): string {
$key = strrev(sprintf('%04d', $id));
public function path(): string {
$key = strrev(sprintf('%04d', $this->id));
return sprintf('%s/%02d/%02d/%d.torrent',
STORAGE_PATH_TORRENT, substr($key, 0, 2), substr($key, 2, 2), $id
STORAGE_PATH_TORRENT, substr($key, 0, 2), substr($key, 2, 2), $this->id
);
}
}

View File

@@ -9,8 +9,8 @@ class RipLog extends \Gazelle\Json {
) {}
public function payload(): array {
$filer = new \Gazelle\File\RipLog();
if (!$filer->exists([$this->torrentId, $this->logId])) {
$ripLog = new \Gazelle\File\RipLog($this->torrentId, $this->logId);
if (!$ripLog->exists()) {
return [
'id' => $this->torrentId,
'logid' => $this->logId,
@@ -32,7 +32,7 @@ class RipLog extends \Gazelle\Json {
];
}
$logFile = $filer->get([$this->torrentId, $this->logId]);
$logFile = $ripLog->get();
$ripLog = new \Gazelle\RipLog($this->torrentId, $this->logId);
return [
'id' => $this->torrentId,

View File

@@ -2,28 +2,29 @@
namespace Gazelle\Manager;
class TorrentLog extends \Gazelle\Base {
public function __construct(
protected \Gazelle\File\RipLog $ripFiler,
protected \Gazelle\File\RipLogHTML $htmlFiler,
) {}
use Gazelle\Logfile as Logfile;
use Gazelle\File\RipLog as RipLog;
use Gazelle\File\RipLogHTML as RipLogHTML;
use Gazelle\Torrent as Torrent;
public function create(\Gazelle\Torrent $torrent, \Gazelle\Logfile $logfile, string $checkerVersion): \Gazelle\TorrentLog {
class TorrentLog extends \Gazelle\Base {
public function create(Torrent $torrent, Logfile $logfile, string $checkerVersion): \Gazelle\TorrentLog {
self::$db->prepared_query('
INSERT INTO torrents_logs
(TorrentID, Score, `Checksum`, FileName, Ripper, RipperVersion, `Language`, ChecksumState, Details, LogcheckerVersion)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
', $torrent->id, $logfile->score(), $logfile->checksumStatus(), $logfile->filename(),
$logfile->ripper(), $logfile->ripperVersion(), $logfile->language(), $logfile->checksumState(), $logfile->detailsAsString(),
$checkerVersion
', $torrent->id, $logfile->score(), $logfile->checksumStatus(),
$logfile->filename(), $logfile->ripper(), $logfile->ripperVersion(),
$logfile->language(), $logfile->checksumState(),
$logfile->detailsAsString(), $checkerVersion
);
$logId = self::$db->inserted_id();
$this->ripFiler->put($logfile->filepath(), [$torrent->id, $logId]);
$this->htmlFiler->put($logfile->text(), [$torrent->id, $logId]);
new RipLog($torrent->id, $logId)->put($logfile->filepath());
new RipLogHTML($torrent->id, $logId)->put($logfile->text());
return $this->findById($torrent, $logId);
}
public function findById(\Gazelle\Torrent $torrent, int $id): ?\Gazelle\TorrentLog {
public function findById(Torrent $torrent, int $id): ?\Gazelle\TorrentLog {
$logId = (int)self::$db->scalar("
SELECT LogID FROM torrents_logs WHERE TorrentID = ? AND LogID = ?
", $torrent->id, $id

View File

@@ -97,8 +97,7 @@ class Torrent extends TorrentAbstract {
* Convert a stored torrent into a binary file that can be loaded in a torrent client
*/
public function torrentBody(string $announceUrl): string {
$filer = new File\Torrent();
$contents = $filer->get($this->id);
$contents = new File\Torrent($this->id)->get();
if ($contents == false) {
return '';
}
@@ -209,11 +208,7 @@ class Torrent extends TorrentAbstract {
*
* @return int number of logfiles removed
*/
public function removeAllLogs(
User $user,
File\RipLog $ripLog,
File\RipLogHTML $ripLogHtml,
): int {
public function removeAllLogs(User $user): int {
self::$db->begin_transaction();
self::$db->prepared_query("
DELETE FROM torrents_logs WHERE TorrentID = ?
@@ -232,9 +227,8 @@ class Torrent extends TorrentAbstract {
$this->logger()->torrent($this, $user, "All logs removed from torrent");
self::$db->commit();
$this->flush();
$ripLog->remove([$this->id, null]);
$ripLogHtml->remove([$this->id, null]);
new File\RipLog($this->id, '*')->remove();
new File\RipLogHTML($this->id, '*')->remove();
return $affected;
}
@@ -457,8 +451,8 @@ class Torrent extends TorrentAbstract {
*
* @return int number of files regenned
*/
public function regenerateFilelist(File\Torrent $filer, \OrpheusNET\BencodeTorrent\BencodeTorrent $encoder): int {
$torrentFile = $filer->get($this->id);
public function regenerateFilelist(\OrpheusNET\BencodeTorrent\BencodeTorrent $encoder): int {
$torrentFile = new File\Torrent($this->id)->get();
if ($torrentFile === false) {
return 0;
}

View File

@@ -2,6 +2,8 @@
namespace Gazelle\Torrent;
use Gazelle\File\RipLogHTML as RipLogHTML;
class Log extends \Gazelle\Base {
public function __construct(
public readonly int $id,
@@ -37,7 +39,6 @@ class Log extends \Gazelle\Base {
);
$logs = self::$db->to_array('LogID', MYSQLI_ASSOC);
$details = [];
$htmlFiler = new \Gazelle\File\RipLogHTML();
foreach ($logs as $log) {
$details[$log['LogID']] = [
'adjustment' => !$log['is_adjusted']
@@ -48,7 +49,7 @@ class Log extends \Gazelle\Base {
'adjusted' => $log['AdjustedScore'],
'reason' => empty($log['AdjustmentReason']) ? 'none supplied' : $log['AdjustmentReason'],
],
'log' => $htmlFiler->get([$this->id, $log['LogID']]),
'log' => new RipLogHTML($this->id, $log['LogID'])->get(),
'status' => array_merge(explode("\n", $log['Details']), unserialize($log['AdjustmentDetails'])),
];
if (($log['Adjusted'] === '0' && $log['Checksum'] === '0') || ($log['Adjusted'] === '1' && $log['AdjustedChecksum'] === '0')) {

View File

@@ -5,6 +5,8 @@ namespace Gazelle;
use Gazelle\Enum\LeechReason;
use Gazelle\Enum\LeechType;
use Gazelle\Enum\TorrentFlag;
use Gazelle\File\RipLog as RipLog;
use Gazelle\File\RipLogHTML as RipLogHTML;
abstract class TorrentAbstract extends BaseAttrObject {
final public const CACHE_LOCK = 'torrent_lock_%d';
@@ -481,7 +483,7 @@ abstract class TorrentAbstract extends BaseAttrObject {
return $this->info()['LogScore'];
}
public function logfileList(File\RipLog $ripFiler, File\RipLogHTML $htmlFiler): array {
public function logfileList(): array {
self::$db->prepared_query("
SELECT LogID AS id,
Score,
@@ -499,13 +501,13 @@ abstract class TorrentAbstract extends BaseAttrObject {
);
$list = self::$db->to_array(false, MYSQLI_ASSOC);
foreach ($list as &$log) {
$log['has_riplog'] = $ripFiler->exists([$this->id, $log['id']]);
$log['html_log'] = $htmlFiler->get([$this->id, $log['id']]);
$log['has_riplog'] = new RipLog($this->id, $log['id'])->exists();
$log['html_log'] = new RipLogHTML($this->id, $log['id'])->get();
$log['adjustment_details'] = unserialize($log['AdjustmentDetails']);
$log['adjusted'] = ($log['Adjusted'] === '1');
$log['adjusted_checksum'] = ($log['AdjustedChecksum'] === '1');
$log['checksum'] = ($log['Checksum'] === '1');
$log['details'] = empty($log['Details']) ? [] : explode("\r\n", trim($log['Details']));
$log['adjusted'] = ($log['Adjusted'] === '1');
$log['adjusted_checksum'] = ($log['AdjustedChecksum'] === '1');
$log['checksum'] = ($log['Checksum'] === '1');
$log['details'] = explode("\r\n", trim($log['Details'] ?? ''));
if ($log['adjusted'] && $log['checksum'] !== $log['adjusted_checksum']) {
$log['details'][] = 'Bad/No Checksum(s)';
}

View File

@@ -22,9 +22,7 @@ $errLog = 0;
$newHtml = 0;
$errHtml = 0;
$db = Gazelle\DB::DB();
$logFiler = new Gazelle\File\RipLog();
$htmlFiler = new Gazelle\File\RipLogHTML();
$db = Gazelle\DB::DB();
while (true) {
$db->prepared_query('
@@ -49,10 +47,10 @@ while (true) {
++$newLog;
}
if (!file_exists($htmlFiler->path([$torrentId, $logId]))) {
if (!$htmlFiler->put($log, [$torrentId, $logId])) {
if (!new RipLog($torrentId, $logId)->put($log)) {
++$errHtml;
}
$htmlFiler->put($log . "\n", [$torrentId, $logId]);
new RipLogHTML($torrentId, $logId)->put("$log\n");
++$newHtml;
}
}

View File

@@ -21,7 +21,6 @@ require_once __DIR__ . '/../lib/bootstrap.php';
$allConfig = [
'-html' => [
'CHECK' => 'SELECT Log FROM torrents_logs WHERE TorrentID = ? AND LogID = ?',
'FILER' => new Gazelle\File\RipLogHTML(),
'HASH' => 'SELECT Log AS digest FROM torrents_logs WHERE TorrentID = ? AND LogID = ?',
'PIPE' => '/usr/bin/find ' . STORAGE_PATH_RIPLOGHTML . ' -type f',
'MATCH' => '~/(\d+)_(\d+)\.html$~',
@@ -29,7 +28,6 @@ $allConfig = [
],
'-log' => [
'CHECK' => 'SELECT 1 FROM torrents_logs WHERE TorrentID = ? AND LogID = ?',
'FILER' => new Gazelle\File\RipLog(),
'HASH' => null,
'PIPE' => '/usr/bin/find ' . STORAGE_PATH_RIPLOG . ' -type f',
'MATCH' => '~/(\d+)_(\d+)\.log$~',
@@ -37,7 +35,6 @@ $allConfig = [
],
'-torrent' => [
'CHECK' => 'SELECT 1 FROM torrents WHERE ID = ?',
'FILER' => new Gazelle\File\Torrent(),
'HASH' => 'SELECT File AS digest FROM torrents_files WHERE TorrentID = ?',
'PIPE' => '/usr/bin/find ' . STORAGE_PATH_TORRENT . ' -type f',
'MATCH' => '~/(\d+)\.torrent$~',

View File

@@ -4,7 +4,6 @@ require_once __DIR__ . '/../lib/bootstrap.php';
$Cache->disableLocalCache();
$torMan = new Gazelle\Manager\Torrent();
$filer = new Gazelle\File\Torrent();
$encoder = new OrpheusNET\BencodeTorrent\BencodeTorrent();
$db = Gazelle\DB::DB();
$max = $db->scalar("SELECT max(ID) FROM torrents");
@@ -22,7 +21,7 @@ while ($id < $max) {
$list = $db->collect(0);
foreach ($list as $id) {
try {
$torMan->findById($id)->regenerateFilelist($filer, $encoder);
$torMan->findById($id)->regenerateFilelist($encoder);
} catch (RuntimeException $e) {
echo "$id: fail: " . $e->getMessage() . "\n";
}

View File

@@ -65,9 +65,10 @@ defined('TMPDIR') or define('TMPDIR', '/tmp');
// Paths for storing uploaded assets outside the database. See bin/generate-storage-dirs
// Should also be moved out of the root partition.
defined('STORAGE_PATH_TORRENT') or define('STORAGE_PATH_TORRENT', '/var/lib/gazelle/torrent');
defined('STORAGE_PATH_GENERAL') or define('STORAGE_PATH_GENERAL', '/var/lib/gazelle/general');
defined('STORAGE_PATH_RIPLOG') or define('STORAGE_PATH_RIPLOG', '/var/lib/gazelle/riplog');
defined('STORAGE_PATH_RIPLOGHTML') or define('STORAGE_PATH_RIPLOGHTML', '/var/lib/gazelle/riploghtml');
defined('STORAGE_PATH_TORRENT') or define('STORAGE_PATH_TORRENT', '/var/lib/gazelle/torrent');
// Host static assets (images, css, js) on another server.
// In development it is just a folder
@@ -134,16 +135,20 @@ defined('AUTHKEY') or define('AUTHKEY', 'changeme');
// Extra salt added into RSS authentication
defined('RSS_HASH') or define('RSS_HASH', 'changeme');
// Seedbox ids are hashified with this salt.
defined('SEEDBOX_SALT') or define('SEEDBOX_SALT', 'changeme');
// User avatars are hashed with this salt.
defined('AVATAR_SALT') or define('AVATAR_SALT', 'changeme');
// General storage files are hashed with this salt
// Changing this after site launch will expire all short-term links
defined('FILE_SALT') or define('FILE_SALT', 'changeme');
// Salt for folder clashes. Changing this after site launch will
// invalidate cache keys that will be regenerated on next call.
defined('FOLDER_CLASH_SALT') or define('FOLDER_CLASH_SALT', 'changeme');
// Seedbox ids are hashified with this salt.
defined('SEEDBOX_SALT') or define('SEEDBOX_SALT', 'changeme');
// Salt for custom Top 10 lists. Changing this after site launch will
// invalidate cache keys that will be regenerated on next call.
defined('TOP10_SALT') or define('TOP10_SALT', 'changeme');

View File

@@ -103,9 +103,10 @@ fi
if [ ! -d /var/lib/gazelle/torrent ]; then
echo "Generate file storage directories..."
perl "${CI_PROJECT_DIR}/bin/generate-storage-dirs" /var/lib/gazelle/torrent 2 100
perl "${CI_PROJECT_DIR}/bin/generate-storage-dirs" /var/lib/gazelle/general 1 100
perl "${CI_PROJECT_DIR}/bin/generate-storage-dirs" /var/lib/gazelle/riplog 2 100
perl "${CI_PROJECT_DIR}/bin/generate-storage-dirs" /var/lib/gazelle/riploghtml 2 100
perl "${CI_PROJECT_DIR}/bin/generate-storage-dirs" /var/lib/gazelle/torrent 2 100
chown -R gazelle /var/lib/gazelle
fi

View File

@@ -9,7 +9,7 @@ $torrent = new Manager\Torrent()->findById((int)($_GET['id'] ?? 0));
if (is_null($torrent)) {
json_error('bad parameters');
}
if ($torrent->uploaderId() != $Viewer->id() && !$Viewer->permitted('admin_add_log')) {
if ($torrent->uploaderId() != $Viewer->id && !$Viewer->permitted('admin_add_log')) {
json_error('Not your upload.');
}
if (empty($_FILES) || empty($_FILES['logfiles'])) {
@@ -19,7 +19,7 @@ if (empty($_FILES) || empty($_FILES['logfiles'])) {
echo new Json\AddLog(
$torrent,
$Viewer,
new Manager\TorrentLog(new File\RipLog(), new File\RipLogHTML()),
new Manager\TorrentLog(),
new LogfileSummary($_FILES['logfiles']),
)
->setVersion(1)

View File

@@ -17,7 +17,7 @@ if (is_null($torrent)) {
if ($torrent->media() !== 'CD') {
Error400::error('Media of torrent precludes adding a log.');
}
if ($torrent->uploaderId() != $Viewer->id() && !$Viewer->permitted('admin_add_log')) {
if ($torrent->uploaderId() != $Viewer->id && !$Viewer->permitted('admin_add_log')) {
Error403::error('Not your upload.');
}
@@ -27,13 +27,10 @@ $logfileSummary = new LogfileSummary($_FILES['logfiles']);
if (!$logfileSummary->total()) {
Error400::error("No logfiles uploaded.");
} else {
$ripFiler = new File\RipLog();
$htmlFiler = new File\RipLogHTML();
$torrent->removeLogDb();
$ripFiler->remove([$torrent->id(), null]);
$htmlFiler->remove([$torrent->id(), null]);
$torrentLogManager = new Manager\TorrentLog($ripFiler, $htmlFiler);
new File\RipLog($torrent->id, '*')->remove();
new File\RipLogHTML($torrent->id, '*')->remove();
$torrentLogManager = new Manager\TorrentLog();
$checkerVersion = Logchecker::getLogcheckerVersion();
foreach ($logfileSummary->all() as $logfile) {

View File

@@ -29,8 +29,6 @@ $requestMan = new Manager\Request();
$userMan = new Manager\User();
$search = new Search\Torrent\Report($_GET['view'] ?? '', $_GET['id'] ?? '', $reportTypeMan, $userMan);
$imgProxy = new Util\ImageProxy($Viewer);
$ripFiler = new File\RipLog();
$htmlFiler = new File\RipLogHTML();
$paginator = new Util\Paginator(REPORTS_PER_PAGE, (int)($_GET['page'] ?? 1));
$paginator->setTotal($search->total());
@@ -173,34 +171,36 @@ if ($search->canUnclaim($Viewer)) {
<?php
} else {
foreach ($details as $logId => $info) {
if ($info['adjustment']) {
$adj = $info['adjustment'];
$adjUser = $userMan->findById($adj['userId']);
?>
if ($info['adjustment']) {
$adj = $info['adjustment'];
$adjUser = $userMan->findById($adj['userId']);
?>
<li>Log adjusted <?= $adjUser ? "by {$adjUser->link()}" : '' ?> from score <?= $adj['score']
?> to <?= $adj['adjusted'] . ($adj['reason'] ? ', reason: ' . $adj['reason'] : '') ?></li>
<?php
}
if (isset($info['status']['tracks'])) {
$info['status']['tracks'] = implode(', ', array_keys($info['status']['tracks']));
}
foreach ($info['status'] as $s) {
if ($s) {
?>
?> to <?= $adj['adjusted'] . ($adj['reason'] ? ', reason: ' . $adj['reason'] : '') ?></li>
<?php
}
if (isset($info['status']['tracks'])) {
$info['status']['tracks'] = implode(', ', array_keys($info['status']['tracks']));
}
foreach ($info['status'] as $s) {
if ($s) {
?>
<li><?= $s ?></li>
<?php
}
}
?>
<?php
}
}
?>
<li>
<span class="nobr"><strong>Logfile #<?= $logId ?></strong>: </span>
<a href="javascript:void(0);" onclick="BBCode.spoiler(this);" class="brackets">Show</a><pre class="hidden"><?= $ripFiler->get([ $torrentId , $logId]) ?></pre>
<a href="javascript:void(0);" onclick="BBCode.spoiler(this);" class="brackets">Show</a><pre class="hidden"><?=
new File\RipLog($torrentId, $logId)->get() ?></pre>
</li>
<li>
<span class="nobr"><strong>HTML logfile #<?= $logId ?></strong>: </span>
<a href="javascript:void(0);" onclick="BBCode.spoiler(this);" class="brackets">Show</a><pre class="hidden"><?= $info['log'] ?></pre>
<a href="javascript:void(0);" onclick="BBCode.spoiler(this);" class="brackets">Show</a><pre class="hidden"><?=
$info['log'] ?></pre>
</li>
<?php
<?php
}
}
?>
@@ -210,37 +210,39 @@ if ($search->canUnclaim($Viewer)) {
<?php
$log = new Torrent\Log($extra->id);
$details = $log->logDetails();
?>
?>
<ul class="nobullet logdetails">
<?php if (!count($details)) { ?>
<?php if (!count($details)) { ?>
<li class="nobr">No logs</li>
<?php
<?php
} else {
foreach ($details as $logId => $info) {
if ($info['adjustment']) {
$adj = $info['adjustment'];
$adjUser = $userMan->findById($adj['userId']);
?>
if ($info['adjustment']) {
$adj = $info['adjustment'];
$adjUser = $userMan->findById($adj['userId']);
?>
<li>Log adjusted <?= $adjUser ? "by {$adjUser->link()}" : '' ?> from score <?= $adj['score']
?> to <?= $adj['adjusted'] . ($adj['reason'] ? ', reason: ' . $adj['reason'] : '') ?></li>
<?php
}
if (isset($info['status']['tracks'])) {
$info['status']['tracks'] = implode(', ', array_keys($info['status']['tracks']));
}
foreach ($info['status'] as $s) {
?>
?> to <?= $adj['adjusted'] . ($adj['reason'] ? ', reason: ' . $adj['reason'] : '') ?></li>
<?php
}
if (isset($info['status']['tracks'])) {
$info['status']['tracks'] = implode(', ', array_keys($info['status']['tracks']));
}
foreach ($info['status'] as $s) {
?>
<li><?= $s ?></li>
<?php } ?>
<?php } ?>
<li>
<span class="nobr"><strong>Raw logfile #<?= $logId ?></strong>: </span>
<a href="javascript:void(0);" onclick="BBCode.spoiler(this);" class="brackets">Show</a><pre class="hidden"><?= $ripFiler->get([$extra->id, $logId]) ?></pre>
<a href="javascript:void(0);" onclick="BBCode.spoiler(this);" class="brackets">Show</a><pre class="hidden"><?=
new File\RipLog($extra->id, $logId)->get() ?></pre>
</li>
<li>
<span class="nobr"><strong>HTML logfile #<?= $logId ?></strong>: </span>
<a href="javascript:void(0);" onclick="BBCode.spoiler(this);" class="brackets">Show</a><pre class="hidden"><?= $info['log'] ?></pre>
<a href="javascript:void(0);" onclick="BBCode.spoiler(this);" class="brackets">Show</a><pre class="hidden"><?=
$info['log'] ?></pre>
</li>
<?php
<?php
}
}
?>

View File

@@ -209,7 +209,7 @@ $db->begin_transaction(); // It's all or nothing
if (isset($_FILES['logfiles'])) {
$logfileSummary = new LogfileSummary($_FILES['logfiles']);
if ($logfileSummary->total()) {
$torrentLogManager = new Manager\TorrentLog(new File\RipLog(), new File\RipLogHTML());
$torrentLogManager = new Manager\TorrentLog();
$checkerVersion = Logchecker::getLogcheckerVersion();
foreach ($logfileSummary->all() as $logfile) {
$torrentLogManager->create($torrent, $logfile, $checkerVersion);

View File

@@ -14,13 +14,14 @@ $torrent = new Manager\Torrent()->findById((int)($_GET['torrentid'] ?? 0));
if (is_null($torrent)) {
Error404::error();
}
$tlog = new Manager\TorrentLog(new File\RipLog(), new File\RipLogHTML())->findById($torrent, (int)($_GET['logid'] ?? 0));
$tlog = new Manager\TorrentLog()->findById($torrent, (int)($_GET['logid'] ?? 0));
if (is_null($tlog)) {
Error404::error();
}
echo $Twig->render('torrent/edit-log.twig', [
'adjuster' => new Manager\User()->findById($tlog->adjustedByUserId())?->link() ?? 'System',
'adjuster' => new Manager\User()
->findById($tlog->adjustedByUserId())?->link() ?? 'System',
'tlog' => $tlog,
'torrent' => $torrent,

View File

@@ -16,8 +16,8 @@ if (is_null($torrent)) {
}
echo $Twig->render('torrent/riplog.twig', [
'id' => $torrent->id(),
'list' => $torrent->logfileList(new File\RipLog(), new File\RipLogHTML()),
'id' => $torrent->id,
'list' => $torrent->logfileList(),
'log_score' => $torrent->logScore(),
'viewer' => $Viewer,
]);

View File

@@ -14,8 +14,7 @@ if (is_null($torrent)) {
Error404::error();
}
$torrent->regenerateFilelist(
new File\Torrent(),
new \OrpheusNET\BencodeTorrent\BencodeTorrent()
new \OrpheusNET\BencodeTorrent\BencodeTorrent(),
);
header("Location: " . $torrent->location());

View File

@@ -16,11 +16,11 @@ if (is_null($torrent) || !$logId) {
Error404::error();
}
new File\RipLog()->remove([$torrent->id(), $logId]);
new File\RipLog($torrent->id, $logId)->remove();
$torrent->logger()->torrent(
$torrent,
$Viewer,
"Riplog ID $logId removed from torrent {$torrent->id()}",
"Riplog ID $logId removed from torrent {$torrent->id}",
);
$torrent->clearLog($logId);

View File

@@ -7,7 +7,9 @@ namespace Gazelle;
authorize();
if (!$Viewer->permitted('torrents_delete')) {
Error403::error();
Error403::error(
'You are not allowed to delete torrents. Please report the torrent instead.'
);
}
$torrent = new Manager\Torrent()->findById((int)($_GET['torrentid'] ?? 0));
@@ -15,9 +17,5 @@ if (is_null($torrent)) {
Error404::error();
}
$torrent->removeAllLogs(
$Viewer,
new File\RipLog(),
new File\RipLogHTML(),
);
$torrent->removeAllLogs($Viewer);
header('Location: ' . $torrent->location());

View File

@@ -17,9 +17,9 @@ if (is_null($torrent) || !$logId) {
Error404::error();
}
$logpath = new File\RipLog()->path([$torrent->id(), $logId]);
$logpath = new File\RipLog($torrent->id, $logId)->path();
$logfile = new Logfile($logpath, basename($logpath));
new File\RipLogHTML()->put($logfile->text(), [$torrent->id(), $logId]);
new File\RipLogHTML($torrent->id, $logId)->put($logfile->text());
$torrent->rescoreLog($logId, $logfile, Logchecker::getLogcheckerVersion());

View File

@@ -309,14 +309,14 @@ $upload = [
'new' => [], // list of newly created Torrent objects
];
$torrentFiler = new File\Torrent();
$torrent = $torMan->findByInfohash(bin2hex($bencoder->getHexInfoHash()));
$torrent = $torMan->findByInfohash(bin2hex($bencoder->getHexInfoHash()));
if ($torrent) {
if ($torrentFiler->exists($torrent->id)) {
$torrentFile = new File\Torrent($torrent->id);
if ($torrentFile->exists()) {
reportError("The exact same torrent file already exists on the site! {$torrent->link()}");
} else {
// A lost torrent
$torrentFiler->put($bencoder->getEncode(), $torrent->id);
$torrentFile->put($bencoder->getEncode());
reportError("Thank you for fixing this torrent {$torrent->link()}");
}
}
@@ -364,11 +364,12 @@ if ($isMusicUpload) {
$torrent = $torMan->findByInfohash(bin2hex($xbencoder->getHexInfoHash()));
if ($torrent) {
if ($torrentFiler->exists($torrent->id)) {
$torrentFile = new File\Torrent($torrent->id);
if ($torrentFile->exists()) {
reportError("The exact same torrent file already exists on the site! {$torrent->link()}");
} else {
// A lost torrent
$torrentFiler->put($bencoder->getEncode(), $torrent->id);
$torrentFile->put($bencoder->getEncode());
reportError("Thank you for fixing this torrent {$torrent->link()}");
}
}
@@ -588,7 +589,7 @@ foreach ($upload['extra'] as $info) {
$size = number_format($extra->size() / (1024 * 1024), 2);
$upload['new'][] = $extra;
$torrentFiler->put($info['TorEnc'], $extra->id);
new File\Torrent($extra->id)->put($info['TorEnc']);
$extra->logger()->torrent($extra, $Viewer, "uploaded ($size MiB)")
->general("Torrent {$extra->id} ($logName) ($size MiB) was uploaded by " . $Viewer->username());
}
@@ -597,7 +598,7 @@ foreach ($upload['extra'] as $info) {
//--------------- Write Files To Disk ------------------------------------------//
if ($logfileSummary?->total()) {
$torrentLogManager = new Manager\TorrentLog(new File\RipLog(), new File\RipLogHTML());
$torrentLogManager = new Manager\TorrentLog();
$checkerVersion = Logchecker::getLogcheckerVersion();
foreach ($logfileSummary->all() as $logfile) {
$torrentLogManager->create($torrent, $logfile, $checkerVersion);
@@ -608,7 +609,7 @@ $size = number_format($TotalSize / (1024 * 1024), 2);
$torrent->logger()->torrent($torrent, $Viewer, "uploaded ($size MiB)")
->general("Torrent $TorrentID ($logName) ($size MiB) was uploaded by " . $Viewer->username());
if (!$torrentFiler->put($bencoder->getEncode(), $TorrentID)) {
if (!new File\Torrent($TorrentID)->put($bencoder->getEncode())) {
reportError("Internal error saving torrent file. Please report this in the bugs forum.");
}
$db->commit(); // We have a usable upload, any subsequent failures can be repaired ex post facto

View File

@@ -23,7 +23,7 @@ switch ($_GET['type']) {
}
header('Content-type: text/plain');
header("Content-Disposition: inline; filename=\"{$match[1]}_{$match[2]}.txt\"");
echo new File\RipLog()->get([$match[1], $match[2]]);
echo new File\RipLog((int)$match[1], (int)$match[2])->get();
break;
default:
Error404::error();

View File

@@ -5,75 +5,143 @@ namespace Gazelle;
use PHPUnit\Framework\TestCase;
class FileStorageTest extends TestCase {
public function testTorrentPath(): void {
$filer = new File\Torrent();
$this->assertEquals(STORAGE_PATH_TORRENT . '/10/00/1.torrent', $filer->path(1), 'file-torrent-00001');
$this->assertEquals(STORAGE_PATH_TORRENT . '/01/00/10.torrent', $filer->path(10), 'file-torrent-00010');
$this->assertEquals(STORAGE_PATH_TORRENT . '/00/10/100.torrent', $filer->path(100), 'file-torrent-00100');
$this->assertEquals(STORAGE_PATH_TORRENT . '/00/01/1000.torrent', $filer->path(1000), 'file-torrent-01000');
$this->assertEquals(STORAGE_PATH_TORRENT . '/00/00/10000.torrent', $filer->path(10000), 'file-torrent-10000');
$this->assertEquals(STORAGE_PATH_TORRENT . '/10/00/10001.torrent', $filer->path(10001), 'file-torrent-10001');
$this->assertEquals(STORAGE_PATH_TORRENT . '/20/00/10002.torrent', $filer->path(10002), 'file-torrent-10002');
$this->assertEquals(STORAGE_PATH_TORRENT . '/43/21/1234.torrent', $filer->path(1234), 'file-torrent-1234');
$this->assertEquals(STORAGE_PATH_TORRENT . '/53/21/1235.torrent', $filer->path(1235), 'file-torrent-1235');
$this->assertEquals(STORAGE_PATH_TORRENT . '/43/21/81234.torrent', $filer->path(81234), 'file-torrent-81234');
}
public function testRipLogPath(): void {
$filer = new File\RipLog();
$this->assertEquals(STORAGE_PATH_RIPLOG . '/71/00/17_34.log', $filer->path([17, 34]), 'file-riplog-17-14');
$this->assertEquals(STORAGE_PATH_RIPLOG . '/73/16/16137_707.log', $filer->path([16137, 707]), 'file-riplog-16137-707');
$this->assertEquals(STORAGE_PATH_RIPLOG . '/73/16/16137_708.log', $filer->path([16137, 708]), 'file-riplog-16137-708');
$this->assertEquals(
STORAGE_PATH_RIPLOG . '/71/00/17_34.log',
new File\RipLog(17, 34)->path(),
'file-riplog-17-14',
);
$this->assertEquals(
STORAGE_PATH_RIPLOG . '/73/16/16137_707.log',
new File\RipLog(16137, 707)->path(),
'file-riplog-16137-707'
);
$this->assertEquals(
STORAGE_PATH_RIPLOG . '/73/16/16137_708.log',
new File\RipLog(16137, 708)->path(),
'file-riplog-16137-708'
);
}
public function testRipLogHTMLPath(): void {
$filer = new File\RipLogHTML();
$this->assertEquals(STORAGE_PATH_RIPLOGHTML . '/72/00/27_44.html', $filer->path([27, 44]), 'file-html-27-44');
$this->assertEquals(STORAGE_PATH_RIPLOGHTML . '/73/16/26137_807.html', $filer->path([26137, 807]), 'file-html-26137-807');
$this->assertEquals(STORAGE_PATH_RIPLOGHTML . '/73/16/26137_808.html', $filer->path([26137, 808]), 'file-html-26137-808');
$this->assertEquals(
STORAGE_PATH_RIPLOGHTML . '/72/00/27_44.html',
new File\RipLogHTML(27, 44)->path(),
'file-html-27-44'
);
$this->assertEquals(
STORAGE_PATH_RIPLOGHTML . '/73/16/26137_807.html',
new File\RipLogHTML(26137, 807)->path(),
'file-html-26137-807'
);
$this->assertEquals(
STORAGE_PATH_RIPLOGHTML . '/73/16/26137_808.html',
new File\RipLogHTML(26137, 808)->path(),
'file-html-26137-808'
);
}
public function testContestsRipLogHTML(): void {
$filer = new File\RipLogHTML();
$id = [2651337, 306];
$this->assertFalse($filer->exists($id), 'file-h-not-exists');
$this->assertFalse($filer->get($id), 'file-h-not-get');
$this->assertFalse($filer->remove($id), 'file-h-not-remove');
public function testTorrentPath(): void {
$this->assertEquals(
STORAGE_PATH_TORRENT . '/10/00/1.torrent',
new File\Torrent(1)->path(),
'file-torrent-00001'
);
$this->assertEquals(
STORAGE_PATH_TORRENT . '/01/00/10.torrent',
new File\Torrent(10)->path(),
'file-torrent-00010'
);
$this->assertEquals(
STORAGE_PATH_TORRENT . '/00/10/100.torrent',
new File\Torrent(100)->path(),
'file-torrent-00100'
);
$this->assertEquals(
STORAGE_PATH_TORRENT . '/00/01/1000.torrent',
new File\Torrent(1000)->path(),
'file-torrent-01000'
);
$this->assertEquals(
STORAGE_PATH_TORRENT . '/00/00/10000.torrent',
new File\Torrent(10000)->path(),
'file-torrent-10000'
);
$this->assertEquals(
STORAGE_PATH_TORRENT . '/10/00/10001.torrent',
new File\Torrent(10001)->path(),
'file-torrent-10001'
);
$this->assertEquals(
STORAGE_PATH_TORRENT . '/20/00/10002.torrent',
new File\Torrent(10002)->path(),
'file-torrent-10002'
);
$this->assertEquals(
STORAGE_PATH_TORRENT . '/43/21/1234.torrent',
new File\Torrent(1234)->path(),
'file-torrent-1234'
);
$this->assertEquals(
STORAGE_PATH_TORRENT . '/53/21/1235.torrent',
new File\Torrent(1235)->path(),
'file-torrent-1235'
);
$this->assertEquals(
STORAGE_PATH_TORRENT . '/43/21/81234.torrent',
new File\Torrent(81234)->path(),
'file-torrent-81234'
);
}
public function testFileRipLogHTML(): void {
$file = new File\RipLogHTML(2651337, 306);
$this->assertFalse($file->exists(), 'file-h-not-exists');
$this->assertFalse($file->get(), 'file-h-not-get');
$this->assertEquals(0, $file->remove(), 'file-h-not-remove');
$text = "<h1>phpunit test " . randomString() . "</h1>";
$this->assertTrue($filer->put($text, $id), 'file-h-put-ok');
$this->assertTrue($file->put($text), 'file-h-put-ok');
$this->assertTrue($filer->exists($id), 'file-h-exists-ok');
$this->assertEquals($text, $filer->get($id), 'file-h-get-ok');
$this->assertTrue($filer->remove($id), 'file-h-remove-ok');
$this->assertFalse($filer->get($id), 'file-h-get-after-remove');
$this->assertTrue($file->exists(), 'file-h-exists-ok');
$this->assertEquals($text, $file->get(), 'file-h-get-ok');
$this->assertEquals(1, $file->remove(), 'file-h-remove-ok');
$this->assertFalse($file->flush()->get(), 'file-h-get-after-remove');
$this->assertEquals('', $file->link(), 'file-h-link');
$this->assertEquals('', $file->location(), 'file-h-location');
}
public function testContestsRipLog(): void {
public function testFileRipLogBinary(): void {
/**
* = File\RipLog cannot easily be unit-tested, as PHP goes to great
* lengths to ensure there is no funny business happening during file
* uploads and there is no easy way to mock it.
*/
$id = [0, 0];
$filer = new File\RipLog();
$this->assertFalse($filer->exists($id), 'file-r-exists-nok');
$this->assertFalse($filer->get($id), 'file-r-get-nok');
$file = new File\RipLog(0, 0);
$this->assertFalse($file->exists(), 'file-r-exists-nok');
$this->assertFalse($file->get(), 'file-r-get-nok');
$json = new Json\RipLog(0, 0);
$this->assertInstanceOf(Json\RipLog::class, $json, 'json-riplog-class');
$payload = $json->payload();
$this->assertCount(17, $payload, 'json-riplog-payload');
$this->assertFalse($payload['success'], 'json-riplog-success-404');
$this->assertFalse($file->flush()->get(), 'file-riplog-get-after-remove');
$this->assertEquals('', $file->link(), 'file-riplog-link');
$this->assertEquals('', $file->location(), 'file-riplog-location');
}
public function testContestsTorrent(): void {
$filer = new File\Torrent();
public function testFileTorrent(): void {
$file = new File\Torrent(906622);
$text = "This is a phpunit torrent file";
$id = 906622;
$this->assertTrue($filer->put($text, $id), 'file-t-put-ok');
$this->assertEquals($text, $filer->get($id), 'file-t-get-ok');
$this->assertTrue($filer->remove($id), 'file-t-put-ok');
$this->assertTrue($file->put($text), 'file-t-put-ok');
$this->assertEquals($text, $file->get(), 'file-t-get-ok');
$this->assertEquals(1, $file->remove(), 'file-t-put-ok');
$this->assertFalse($file->flush()->get(), 'file-t-get-after-remove');
$this->assertEquals('', $file->link(), 'file-t-link');
$this->assertEquals('', $file->location(), 'file-t-location');
}
}

View File

@@ -92,10 +92,7 @@ class LogcheckerTest extends TestCase {
$this->assertCount(1, $logfileSummary->all(), 'logfilesummary-all');
$this->assertEquals(1, $logfileSummary->total(), 'logfilesummary-total');
$torrentLogManager = new Manager\TorrentLog(
new File\RipLog(),
new File\RipLogHTML()
);
$torrentLogManager = new Manager\TorrentLog();
$checkerVersion = Logchecker::getLogcheckerVersion();
$torrentLog = null;
foreach ($logfileSummary->all() as $logfile) {

View File

@@ -88,13 +88,13 @@ class TorrentTest extends TestCase {
$bencoder->decodeFile(__DIR__ . '/../fixture/valid_torrent.torrent');
$info = $bencoder->getData();
$this->assertIsArray($info, 'torrent-file-data-array');
$torrentFiler = new File\Torrent();
$torrentFile = new File\Torrent($this->torrent->id);
$this->assertTrue(
$torrentFiler->put($bencoder->getEncode(), $this->torrent->id()),
$torrentFile->put($bencoder->getEncode()),
'torrent-file-put'
);
$this->assertTrue(
$torrentFiler->exists($this->torrent->id()),
$torrentFile->exists(),
'torrent-file-exists'
);
@@ -137,8 +137,9 @@ class TorrentTest extends TestCase {
$this->assertEquals(8215612, $totalSize, 'torrent-file-total-size');
$this->assertCount(3, $fileList, 'torrent-file-list');
$this->assertTrue(
$torrentFiler->remove($this->torrent->id()),
$this->assertEquals(
1,
$torrentFile->remove(),
'torrent-file-remove'
);
}
@@ -169,7 +170,7 @@ class TorrentTest extends TestCase {
4,
new Manager\User()->sendRemovalPm(
$this->user,
$torrent->id(),
$torrent->id,
$name,
$path,
log: 'phpunit removal test',
@@ -275,18 +276,14 @@ class TorrentTest extends TestCase {
);
$this->assertEquals(
0,
$this->torrent->removeAllLogs(
$this->user,
new File\RipLog(),
new File\RipLogHTML(),
),
$this->torrent->removeAllLogs($this->user),
'torrent-remove-all-logs'
);
}
public function testTorrentBBCode(): void {
$torrentId = $this->torrent->id();
$tgroupId = $this->torrent->group()->id();
$torrentId = $this->torrent->id;
$tgroupId = $this->torrent->group()->id;
$torrentRegexp = "^<a href=\"artist\.php\?id=\d+\" dir=\"ltr\">.*</a> <a title=\".*?\" href=\"/torrents\.php\?id={$tgroupId}&torrentid={$torrentId}#torrent{$torrentId}\">.* \[\d+ .*?\]</a>";
$this->assertMatchesRegularExpression("@{$torrentRegexp} .*$@",