diff --git a/composer.json b/composer.json index bc98cba..1a6cc49 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,8 @@ { "require": { - "flashwave/index": "^0.2410", - "flashii/apii": "^0.2", - "erusev/parsedown": "~1.6", + "flashwave/index": "^0.2501", + "flashii/apii": "^0.3", + "erusev/parsedown": "^1.7", "sentry/sdk": "^4.0" }, "autoload": { @@ -19,6 +19,6 @@ ] }, "require-dev": { - "phpstan/phpstan": "^1.11" + "phpstan/phpstan": "^2.1" } } diff --git a/composer.lock b/composer.lock index 5c8c6a4..2dba476 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f71663659023233c6bbd47cc74f1d954", + "content-hash": "ac4e8872cae3ee611a65a591d65acb1f", "packages": [ { "name": "erusev/parsedown", @@ -58,11 +58,11 @@ }, { "name": "flashii/apii", - "version": "v0.2.1", + "version": "v0.3.0", "source": { "type": "git", "url": "https://patchii.net/flashii/apii-php.git", - "reference": "6a93d31375dd7e75ff9264f3024f2208ce602f49" + "reference": "2d6c135faddd359341762afcb9c429e279d87059" }, "require": { "php": ">=8.1" @@ -91,25 +91,25 @@ ], "description": "Client library for the Flashii.net API.", "homepage": "https://api.flashii.net", - "time": "2024-11-16T16:03:42+00:00" + "time": "2024-11-22T21:36:01+00:00" }, { "name": "flashwave/index", - "version": "v0.2410.191603", + "version": "v0.2501.182241", "source": { "type": "git", "url": "https://patchii.net/flash/index.git", - "reference": "17cdb4d1c239241200d7e30968122a8cd8b26509" + "reference": "cab5f480db65f8a720c1ad0852423708bc415446" }, "require": { "ext-mbstring": "*", - "php": ">=8.3", - "twig/html-extra": "^3.13", - "twig/twig": "^3.14" + "php": ">=8.4", + "twig/html-extra": "^3.18", + "twig/twig": "^3.18" }, "require-dev": { - "phpstan/phpstan": "^1.11", - "phpunit/phpunit": "^11.2" + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^11.5" }, "suggest": { "ext-memcache": "Support for the Index\\Cache\\Memcached namespace (only if you can't use ext-memcached for some reason).", @@ -146,7 +146,7 @@ ], "description": "Composer package for the common library for my projects.", "homepage": "https://railgun.sh/index", - "time": "2024-10-19T16:04:17+00:00" + "time": "2025-01-18T22:41:28+00:00" }, { "name": "guzzlehttp/psr7", @@ -671,16 +671,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", "shasum": "" }, "require": { @@ -688,12 +688,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -718,7 +718,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" }, "funding": [ { @@ -734,20 +734,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/mime", - "version": "v7.1.6", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "caa1e521edb2650b8470918dfe51708c237f0598" + "reference": "7f9617fcf15cb61be30f8b252695ed5e2bfac283" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/caa1e521edb2650b8470918dfe51708c237f0598", - "reference": "caa1e521edb2650b8470918dfe51708c237f0598", + "url": "https://api.github.com/repos/symfony/mime/zipball/7f9617fcf15cb61be30f8b252695ed5e2bfac283", + "reference": "7f9617fcf15cb61be30f8b252695ed5e2bfac283", "shasum": "" }, "require": { @@ -802,7 +802,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.1.6" + "source": "https://github.com/symfony/mime/tree/v7.2.1" }, "funding": [ { @@ -818,20 +818,20 @@ "type": "tidelift" } ], - "time": "2024-10-25T15:11:02+00:00" + "time": "2024-12-07T08:50:44+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "85e95eeede2d41cd146146e98c9c81d9214cae85" + "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/85e95eeede2d41cd146146e98c9c81d9214cae85", - "reference": "85e95eeede2d41cd146146e98c9c81d9214cae85", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/7da8fbac9dcfef75ffc212235d76b2754ce0cf50", + "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50", "shasum": "" }, "require": { @@ -869,7 +869,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.1.6" + "source": "https://github.com/symfony/options-resolver/tree/v7.2.0" }, "funding": [ { @@ -885,7 +885,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-11-20T11:17:29+00:00" }, { "name": "symfony/polyfill-ctype", @@ -913,8 +913,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -990,8 +990,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -1072,8 +1072,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -1156,8 +1156,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -1230,8 +1230,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -1288,16 +1288,16 @@ }, { "name": "twig/html-extra", - "version": "v3.15.0", + "version": "v3.18.0", "source": { "type": "git", "url": "https://github.com/twigphp/html-extra.git", - "reference": "2086023d3ffc4bae2b1115f715d17f97fd013665" + "reference": "c63b28e192c1b7c15bb60f81d2e48b140846239a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/html-extra/zipball/2086023d3ffc4bae2b1115f715d17f97fd013665", - "reference": "2086023d3ffc4bae2b1115f715d17f97fd013665", + "url": "https://api.github.com/repos/twigphp/html-extra/zipball/c63b28e192c1b7c15bb60f81d2e48b140846239a", + "reference": "c63b28e192c1b7c15bb60f81d2e48b140846239a", "shasum": "" }, "require": { @@ -1340,7 +1340,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/html-extra/tree/v3.15.0" + "source": "https://github.com/twigphp/html-extra/tree/v3.18.0" }, "funding": [ { @@ -1352,20 +1352,20 @@ "type": "tidelift" } ], - "time": "2024-09-30T06:41:48+00:00" + "time": "2024-12-29T10:29:59+00:00" }, { "name": "twig/twig", - "version": "v3.15.0", + "version": "v3.18.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "2d5b3964cc21d0188633d7ddce732dc8e874db02" + "reference": "acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/2d5b3964cc21d0188633d7ddce732dc8e874db02", - "reference": "2d5b3964cc21d0188633d7ddce732dc8e874db02", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50", + "reference": "acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50", "shasum": "" }, "require": { @@ -1376,6 +1376,7 @@ "symfony/polyfill-php81": "^1.29" }, "require-dev": { + "phpstan/phpstan": "^2.0", "psr/container": "^1.0|^2.0", "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" }, @@ -1419,7 +1420,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.15.0" + "source": "https://github.com/twigphp/Twig/tree/v3.18.0" }, "funding": [ { @@ -1431,26 +1432,26 @@ "type": "tidelift" } ], - "time": "2024-11-17T15:59:19+00:00" + "time": "2024-12-29T10:51:50+00:00" } ], "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.12.11", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "0d1fc20a962a91be578bcfe7cf939e6e1a2ff733" + "reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0d1fc20a962a91be578bcfe7cf939e6e1a2ff733", - "reference": "0d1fc20a962a91be578bcfe7cf939e6e1a2ff733", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7", + "reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7", "shasum": "" }, "require": { - "php": "^7.2|^8.0" + "php": "^7.4|^8.0" }, "conflict": { "phpstan/phpstan-shim": "*" @@ -1491,15 +1492,15 @@ "type": "github" } ], - "time": "2024-11-17T14:08:01+00:00" + "time": "2025-01-05T16:43:48+00:00" } ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [], + "platform": {}, + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/public/index.php b/public/index.php index aedf6f5..3ca7086 100644 --- a/public/index.php +++ b/public/index.php @@ -1,6 +1,7 @@ getString('apii:api', FlashiiUrls::PROD_API_URL), - $cfg->getString('apii:id', FlashiiUrls::PROD_ID_URL) + $cfg->getString('apii:api', FlashiiUrls::PROD_API_URL) )); -$authInfo = $flashii->v1()->me(); -if($authInfo !== null) { - $users = $seria->getUsersContext()->getUsers(); - $users->syncApiUser($authInfo); - $sUserInfo = $users->getUser($authInfo->getId(), 'id'); - $seria->getAuthInfo()->setInfo($sUserInfo); -} else $sUserInfo = null; +try { + $authInfo = $flashii->v1()->me(); + $seria->usersCtx->users->syncApiUser($authInfo); + $sUserInfo = $seria->usersCtx->users->getUser($authInfo->getId(), 'id'); + $seria->authInfo->userInfo = $sUserInfo; +} catch(RuntimeException $ex) { + $authInfo = $sUserInfo = null; +} -$seria->startCSRFP( +$seria->initCsrfToken( $cfg->getString('csrfp:secret', 'mewow'), $authInfo === null ? (string)filter_input(INPUT_SERVER, 'REMOTE_ADDR') : $authToken ); diff --git a/src/Auth/AuthInfo.php b/src/Auth/AuthInfo.php index a85aa26..2bef49d 100644 --- a/src/Auth/AuthInfo.php +++ b/src/Auth/AuthInfo.php @@ -5,39 +5,21 @@ use Index\Colour\Colour; use Seria\Users\UserInfo; class AuthInfo { - private ?UserInfo $userInfo; + public ?UserInfo $userInfo = null; - public function __construct() { - $this->setInfo(); + public bool $loggedIn { + get => $this->userInfo !== null; } - public function setInfo( - ?UserInfo $userInfo = null - ): void { - $this->userInfo = $userInfo; + public ?string $userId { + get => $this->userInfo?->id; } - public function removeInfo(): void { - $this->setInfo(); + public ?string $userName { + get => $this->userInfo?->name; } - public function isLoggedIn(): bool { - return $this->userInfo !== null; - } - - public function getUserId(): ?string { - return $this->userInfo?->getId(); - } - - public function getUserName(): ?string { - return $this->userInfo?->getName(); - } - - public function getUserColour(): ?Colour { - return $this->userInfo?->getColour(); - } - - public function getUserInfo(): ?UserInfo { - return $this->userInfo; + public ?Colour $userColour { + get => $this->userInfo?->colour; } } diff --git a/src/Colours.php b/src/Colours.php index a733e47..f99c967 100644 --- a/src/Colours.php +++ b/src/Colours.php @@ -6,16 +6,16 @@ use Index\Colour\Colour; use Index\Colour\ColourRgb; final class Colours { - public const RATIO_GOOD = 0x008000; - public const RATIO_WARN = 0xFFAA00; - public const RATIO_BAD = 0xFF0000; + public const int RATIO_GOOD = 0x008000; + public const int RATIO_WARN = 0xFFAA00; + public const int RATIO_BAD = 0xFF0000; - public const FILE_UP_TO_1GB = 0xA0F5B8; - public const FILE_UP_TO_5GB = 0xBAE9C7; - public const FILE_UP_TO_10GB = 0xCCDDDD; - public const FILE_UP_TO_20GB = 0xCCA1F4; - public const FILE_UP_TO_50GB = 0xDB5FF1; - public const FILE_OVER_50GB = 0xEC32A4; + public const int FILE_UP_TO_1GB = 0xA0F5B8; + public const int FILE_UP_TO_5GB = 0xBAE9C7; + public const int FILE_UP_TO_10GB = 0xCCDDDD; + public const int FILE_UP_TO_20GB = 0xCCA1F4; + public const int FILE_UP_TO_50GB = 0xDB5FF1; + public const int FILE_OVER_50GB = 0xEC32A4; private static array $colourCache = []; diff --git a/src/HomeRoutes.php b/src/HomeRoutes.php index 5f28573..5b30e00 100644 --- a/src/HomeRoutes.php +++ b/src/HomeRoutes.php @@ -1,11 +1,12 @@ redirect('/', true); } } diff --git a/src/RoutingContext.php b/src/RoutingContext.php index 1a03e5d..43c4ba2 100644 --- a/src/RoutingContext.php +++ b/src/RoutingContext.php @@ -12,10 +12,6 @@ class RoutingContext { $this->router->registerContentHandler(new StringableContentHandler); // this should really be in Index lol but i can't be bothered rn! } - public function getRouter(): Router { - return $this->router; - } - public function register(RouteHandler $handler): void { $this->router->register($handler); } diff --git a/src/RoutingErrorHandler.php b/src/RoutingErrorHandler.php index 2a89b9a..4205aa4 100644 --- a/src/RoutingErrorHandler.php +++ b/src/RoutingErrorHandler.php @@ -11,21 +11,20 @@ class RoutingErrorHandler implements HttpErrorHandler { public function handle(HttpResponseBuilder $response, HttpRequest $request, int $code, string $message): void { if($code === 500) { $response->setTypeHTML(); - $response->setContent(file_get_contents(SERIA_DIR_TEMPLATES . '/500.html')); + $response->content = file_get_contents(SERIA_DIR_TEMPLATES . '/500.html'); return; } - $templating = $this->context->getTemplating(); - if($templating === null) { + if($this->context->templating === null) { $response->setTypePlain(); - $response->setContent((string)$code); + $response->content = (string)$code; return; } $response->setTypeHTML(); - $response->setContent($templating->render('http-error', [ + $response->content = $this->context->templating->render('http-error', [ 'http_code' => $code, 'http_text' => $message, - ])); + ]); } } diff --git a/src/SeriaContext.php b/src/SeriaContext.php index 53d8ccf..9161e5f 100644 --- a/src/SeriaContext.php +++ b/src/SeriaContext.php @@ -12,22 +12,19 @@ use Seria\Torrents\TorrentsContext; use Seria\Users\UsersContext; final class SeriaContext { - private DbConnection $dbConn; - private Config $config; - private ?TplEnvironment $templating = null; + public private(set) AuthInfo $authInfo; + public private(set) SiteInfo $siteInfo; - private AuthInfo $authInfo; - private SiteInfo $siteInfo; + public private(set) CsrfToken $csrf; + public private(set) ?TplEnvironment $templating = null; - private CsrfToken $csrfp; - - private TorrentsContext $torrentsCtx; - private UsersContext $usersCtx; - - public function __construct(DbConnection $dbConn, Config $config) { - $this->dbConn = $dbConn; - $this->config = $config; + public private(set) TorrentsContext $torrentsCtx; + public private(set) UsersContext $usersCtx; + public function __construct( + private DbConnection $dbConn, + private Config $config + ) { $this->authInfo = new AuthInfo; $this->siteInfo = new SiteInfo($config->scopeTo('site')); @@ -35,10 +32,6 @@ final class SeriaContext { $this->usersCtx = new UsersContext($dbConn); } - public function getDbConn(): DbConnection { - return $this->dbConn; - } - public function getDbQueryCount(): int { $result = $this->dbConn->query('SHOW SESSION STATUS LIKE "Questions"'); return $result->next() ? $result->getInteger(1) : 0; @@ -52,39 +45,15 @@ final class SeriaContext { return new FsDbMigrationRepo(SERIA_DIR_MIGRATIONS); } - public function getAuthInfo(): AuthInfo { - return $this->authInfo; - } - - public function getSiteInfo(): SiteInfo { - return $this->siteInfo; - } - - public function getTorrentsContext(): TorrentsContext { - return $this->torrentsCtx; - } - - public function getUsersContext(): UsersContext { - return $this->usersCtx; - } - - public function getTemplating(): ?TplEnvironment { - return $this->templating; - } - - public function getCSRFP(): CsrfToken { - return $this->csrfp; - } - - public function startCSRFP(string $secretKey, string $identity): void { - $this->csrfp = new CsrfToken($secretKey, $identity); + public function initCsrfToken(string $secretKey, string $identity): void { + $this->csrf = new CsrfToken($secretKey, $identity); } public function startTemplating(): void { $globals = [ 'auth_info' => $this->authInfo, 'site_info' => $this->siteInfo, - 'display_timings_info' => SERIA_DEBUG, // + isFlash + 'display_timings_info' => SERIA_DEBUG, ]; $this->templating = new TplEnvironment( @@ -101,18 +70,18 @@ final class SeriaContext { $routing->register(new HomeRoutes($this->templating)); $routing->register(new Users\ProfileRoutes($this->authInfo, $this->torrentsCtx, $this->usersCtx, $this->templating)); - $routing->register(new Users\SettingsRoutes($this->authInfo, $this->usersCtx, $this->csrfp, $this->templating)); - $routing->register(new Torrents\AnnounceRouting($this->torrentsCtx, $this->usersCtx)); - $routing->register(new Torrents\TorrentCreateRouting($this->dbConn, $this->authInfo, $this->torrentsCtx, $this->csrfp, $this->templating)); - $routing->register(new Torrents\TorrentInfoRouting( + $routing->register(new Users\SettingsRoutes($this->authInfo, $this->usersCtx, $this->csrf, $this->templating)); + $routing->register(new Torrents\AnnounceRoutes($this->torrentsCtx, $this->usersCtx)); + $routing->register(new Torrents\TorrentCreateRoutes($this->dbConn, $this->authInfo, $this->torrentsCtx, $this->csrf, $this->templating)); + $routing->register(new Torrents\TorrentInfoRoutes( $this->config->scopeTo('announce'), $this->authInfo, $this->torrentsCtx, $this->usersCtx, - $this->csrfp, + $this->csrf, $this->templating )); - $routing->register(new Torrents\TorrentListRouting($this->authInfo, $this->torrentsCtx, $this->usersCtx, $this->templating)); + $routing->register(new Torrents\TorrentListRoutes($this->authInfo, $this->torrentsCtx, $this->usersCtx, $this->templating)); return $routing; } diff --git a/src/SiteInfo.php b/src/SiteInfo.php index 1fedf5f..0e68b57 100644 --- a/src/SiteInfo.php +++ b/src/SiteInfo.php @@ -7,32 +7,32 @@ use Seria\Users\UserInfo; class SiteInfo { public function __construct(private Config $config) {} - public function getName(): string { - return $this->config->getString('name'); + public string $name { + get => $this->config->getString('name'); } - public function getHost(): string { - return $this->config->getString('host'); + public string $host { + get => $this->config->getString('host'); } - public function getMainSiteName(): string { - return $this->config->getString('parent'); + public string $mainSiteName { + get => $this->config->getString('parent'); } - public function getLoginUrl(): string { - return $this->config->getString('login'); + public string $loginUrl { + get => $this->config->getString('login'); } public function getProfileUrl(UserInfo|string $userInfo): string { if($userInfo instanceof UserInfo) - $userInfo = $userInfo->getId(); + $userInfo = $userInfo->id; return sprintf($this->config->getString('profile'), $userInfo); } public function getAvatarUrl(UserInfo|string $userInfo, int $res = 0): string { if($userInfo instanceof UserInfo) - $userInfo = $userInfo->getId(); + $userInfo = $userInfo->id; return sprintf($this->config->getString($res < 1 ? 'avatar' : 'avatar:res'), $userInfo, $res); } diff --git a/src/StringableContentHandler.php b/src/StringableContentHandler.php index 5f57e58..1757f5c 100644 --- a/src/StringableContentHandler.php +++ b/src/StringableContentHandler.php @@ -11,6 +11,6 @@ class StringableContentHandler implements HttpContentHandler { /** @param Stringable $content */ public function handle(HttpResponseBuilder $response, mixed $content): void { - $response->setContent(new StringHttpContent((string)$content)); + $response->content = new StringHttpContent((string)$content); } } diff --git a/src/TemplatingExtension.php b/src/TemplatingExtension.php index e80a43d..2b35cb3 100644 --- a/src/TemplatingExtension.php +++ b/src/TemplatingExtension.php @@ -6,18 +6,14 @@ use Twig\TwigFunction; use Seria\Torrents\TorrentPeers; final class TemplatingExtension extends AbstractExtension { - private TorrentPeers $peers; - public function __construct( private SeriaContext $ctx, private SiteInfo $siteInfo - ) { - $this->peers = $ctx->getTorrentsContext()->getPeers(); - } + ) {} public function getFunctions() { return [ - new TwigFunction('csrfp_token', $this->ctx->getCSRFP()->createToken(...)), + new TwigFunction('csrfp_token', $this->ctx->csrf->createToken(...)), new TwigFunction('git_commit_hash', GitInfo::hash(...)), new TwigFunction('git_tag', GitInfo::tag(...)), new TwigFunction('git_branch', GitInfo::branch(...)), @@ -26,16 +22,15 @@ final class TemplatingExtension extends AbstractExtension { new TwigFunction('seria_header_menu', $this->getHeaderMenu(...)), new TwigFunction('seria_ratio_colour', Colours::forRatio(...)), new TwigFunction('seria_filesize_colour', Colours::forFileSize(...)), - new TwigFunction('seria_count_user_uploading', $this->peers->countUserUploading(...)), - new TwigFunction('seria_count_user_downloading', $this->peers->countUserDownloading(...)), + new TwigFunction('seria_count_user_uploading', $this->ctx->torrentsCtx->peers->countUserUploading(...)), + new TwigFunction('seria_count_user_downloading', $this->ctx->torrentsCtx->peers->countUserDownloading(...)), ]; } public function getHeaderMenu(): array { $menu = []; - $authInfo = $this->ctx->getAuthInfo(); - if($authInfo->isLoggedIn()) + if($this->ctx->authInfo->loggedIn) $menu[] = [ 'text' => 'Settings', 'url' => '/settings', @@ -43,7 +38,7 @@ final class TemplatingExtension extends AbstractExtension { else $menu[] = [ 'text' => 'Log in', - 'url' => $this->siteInfo->getLoginUrl(), + 'url' => $this->siteInfo->loginUrl, ]; $menu[] = [ @@ -51,16 +46,14 @@ final class TemplatingExtension extends AbstractExtension { 'url' => '/available', ]; - if($authInfo->isLoggedIn()) { - $userInfo = $authInfo->getUserInfo(); - - if($userInfo->canCreateTorrents()) + if($this->ctx->authInfo->loggedIn) { + if($this->ctx->authInfo->userInfo->canCreateTorrents) $menu[] = [ 'text' => 'Create Torrent', 'url' => '/create', ]; - if($userInfo->canApproveTorrents()) + if($this->ctx->authInfo->userInfo->canApproveTorrents) $menu[] = [ 'text' => 'Pending Torrents', 'url' => '/pending', diff --git a/src/Torrents/AnnounceEmpty.php b/src/Torrents/AnnounceEmpty.php index 1762caa..22d95c8 100644 --- a/src/Torrents/AnnounceEmpty.php +++ b/src/Torrents/AnnounceEmpty.php @@ -1,37 +1,29 @@ compactPeers ? '' : []; + #[BencodeProperty] + public string|array $peers { + get => $this->compactPeers ? '' : []; } } diff --git a/src/Torrents/AnnounceFailure.php b/src/Torrents/AnnounceFailure.php index 2b82584..dcfa78b 100644 --- a/src/Torrents/AnnounceFailure.php +++ b/src/Torrents/AnnounceFailure.php @@ -1,15 +1,13 @@ reason; - } + public function __construct( + #[BencodeProperty('failure reason')] + public private(set) string $reason + ) {} } diff --git a/src/Torrents/AnnounceInfo.php b/src/Torrents/AnnounceInfo.php index 2073a7a..e1fdf60 100644 --- a/src/Torrents/AnnounceInfo.php +++ b/src/Torrents/AnnounceInfo.php @@ -1,78 +1,48 @@ seed ? ++$this->complete : ++$this->incomplete; + } private function createCompactPeersList(): string { - $peers = ''; - foreach($this->peers as $peerInfo) { - $peerInfo->isSeed() ? ++$this->completePeers : ++$this->incompletePeers; - $peers .= $peerInfo->getAddressRaw() . pack('n', $peerInfo->getPort()); - } - return $peers; + return implode('', XArray::select($this->peers, fn($peerInfo) => ($peerInfo->addressRaw . pack('n', $peerInfo->port)))); } private function createPeersListWithIds(): array { - $peers = []; - foreach($this->peers as $peerInfo) { - $peerInfo->isSeed() ? ++$this->completePeers : ++$this->incompletePeers; - $peers[] = [ - 'peer id' => $peerInfo->getId(), - 'ip' => $peerInfo->getAddress(), - 'port' => $peerInfo->getPort(), - ]; - } - - return $peers; + return XArray::select($this->peers, fn($peerInfo) => [ + 'peer id' => $peerInfo->id, + 'ip' => $peerInfo->address, + 'port' => $peerInfo->port, + ]); } private function createPeersListWithoutIds(): array { - $peers = []; - foreach($this->peers as $peerInfo) { - $peerInfo->isSeed() ? ++$this->completePeers : ++$this->incompletePeers; - $peers[] = [ - 'ip' => $peerInfo->getAddress(), - 'port' => $peerInfo->getPort(), - ]; - } - - return $peers; - } - - #[BencodeProperty('interval')] - public function getInterval(): int { - return $this->interval; - } - - #[BencodeProperty('min interval')] - public function getMinimumInterval(): int { - return $this->minInterval; - } - - #[BencodeProperty('complete')] - public function getComplete(): int { - return $this->completePeers; - } - - #[BencodeProperty('incomplete')] - public function getIncomplete(): int { - return $this->incompletePeers; + return XArray::select($this->peers, fn($peerInfo) => [ + 'ip' => $peerInfo->address, + 'port' => $peerInfo->port, + ]); } // #[BencodeProperty('downloaded')] diff --git a/src/Torrents/AnnounceRouting.php b/src/Torrents/AnnounceRoutes.php similarity index 75% rename from src/Torrents/AnnounceRouting.php rename to src/Torrents/AnnounceRoutes.php index 41d13d9..2195ada 100644 --- a/src/Torrents/AnnounceRouting.php +++ b/src/Torrents/AnnounceRoutes.php @@ -3,11 +3,12 @@ namespace Seria\Torrents; use RuntimeException; use Index\Bencode\Bencode; -use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerTrait}; +use Index\Http\{HttpResponseBuilder,HttpRequest}; +use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerCommon}; use Seria\Users\UsersContext; -class AnnounceRouting implements RouteHandler { - use RouteHandlerTrait; +class AnnounceRoutes implements RouteHandler { + use RouteHandlerCommon; public const INTERVAL = 1800; public const INTERVAL_MIN = 30; @@ -21,19 +22,14 @@ class AnnounceRouting implements RouteHandler { #[HttpGet('/announce.php')] #[HttpGet('/announce/([A-Za-z0-9]+)')] #[HttpGet('/announce.php/([A-Za-z0-9]+)')] - public function getAnnounce($response, $request, string $key = '') { - $remoteAddr = $request->getRemoteAddress(); - if(strlen(inet_pton($remoteAddr)) !== 4) + public function getAnnounce(HttpResponseBuilder $response, HttpRequest $request, string $key = '') { + if(strlen(inet_pton($request->remoteAddress)) !== 4) return new AnnounceFailure('Tracker is only supported over IPv4, please reset your DNS cache.'); - $torrents = $this->torrentsCtx->getTorrents(); - $peers = $this->torrentsCtx->getPeers(); - $users = $this->usersCtx->getUsers(); - $userInfo = null; if($key !== '') try { - $userInfo = $users->getUser($key, 'passkey'); + $userInfo = $this->usersCtx->users->getUser($key, 'passkey'); } catch(RuntimeException $ex) { sleep(3); return new AnnounceFailure('Authentication failed.'); @@ -78,7 +74,7 @@ class AnnounceRouting implements RouteHandler { $wantsPeerAmount = (int)$request->getParam('numwant', FILTER_SANITIZE_NUMBER_INT); try { - $torrentInfo = $torrents->getTorrent($infoHash, 'hash'); + $torrentInfo = $this->torrentsCtx->torrents->getTorrent($infoHash, 'hash'); } catch(RuntimeException $ex) { return new AnnounceFailure('Info hash not found.'); } @@ -92,12 +88,12 @@ class AnnounceRouting implements RouteHandler { default => $canDownload, }); - $peerInfo = $peers->getPeer($torrentInfo, $peerId); + $peerInfo = $this->torrentsCtx->peers->getPeer($torrentInfo, $peerId); if($peerInfo === null) { // could probably skip this is the event is 'stopped' - $peerInfo = $peers->createPeer( - $torrentInfo, $userInfo, $peerId, $remoteAddr, $port, self::INTERVAL, + $peerInfo = $this->torrentsCtx->peers->createPeer( + $torrentInfo, $userInfo, $peerId, $request->remoteAddress, $port, self::INTERVAL, $userAgent, $key, $bytesUploaded, $bytesDownloaded, $bytesRemaining ); } else { @@ -112,26 +108,26 @@ class AnnounceRouting implements RouteHandler { } if($userInfo !== null) - $users->incrementTransferStats( + $this->usersCtx->users->incrementTransferStats( $userInfo, - $bytesDownloaded - $peerInfo->getBytesDownloaded(), - $bytesUploaded - $peerInfo->getBytesUploaded() + $bytesDownloaded - $peerInfo->bytesDownloaded, + $bytesUploaded - $peerInfo->bytesUploaded ); - $peers->updatePeer( - $torrentInfo, $peerInfo, $remoteAddr, $port, self::INTERVAL, + $this->torrentsCtx->peers->updatePeer( + $torrentInfo, $peerInfo, $request->remoteAddress, $port, self::INTERVAL, $userAgent, $bytesUploaded, $bytesDownloaded, $bytesRemaining ); } if($event === 'stopped') { - $peers->deletePeer($torrentInfo, $peerInfo); + $this->torrentsCtx->peers->deletePeer($torrentInfo, $peerInfo); return new AnnounceEmpty($compact); } - $peers->pruneExpiredPeers(); + $this->torrentsCtx->peers->pruneExpiredPeers(); - $peerInfos = $peers->getPeers($torrentInfo); + $peerInfos = $this->torrentsCtx->peers->getPeers($torrentInfo); return new AnnounceInfo( $peerInfos, diff --git a/src/Torrents/TorrentBuilder.php b/src/Torrents/TorrentBuilder.php index dad3b8e..5635081 100644 --- a/src/Torrents/TorrentBuilder.php +++ b/src/Torrents/TorrentBuilder.php @@ -9,60 +9,48 @@ use Index\Db\DbConnection; use Seria\Users\UserInfo; class TorrentBuilder { - private ?string $userId = null; - private string $name = ''; - private int $created; - private int $pieceLength = 0; - private bool $isPrivate = false; - private string $comment = ''; - private array $files = []; - private array $pieces = []; - public function __construct() { - $this->created = time(); + $this->createdTime = time(); } - public function setUser(?UserInfo $userInfo): self { - return $this->setUserId($userInfo?->getId()); + public ?string $userId = null; + + public string $name = '' { + get => $this->name; + set(string $name) { + if(empty($name)) + throw new InvalidArgumentException('$name may not be empty.'); + + $this->name = $name; + } } - public function setUserId(?string $userId): self { - $this->userId = $userId; - return $this; + public int $createdTime { + get => $this->createdTime; + set(int $createdTime) { + if($createdTime < 0 || $createdTime > 0x7FFFFFFF) + throw new InvalidArgumentException('$createdTime is not a valid timestamp.'); + + $this->createdTime = $createdTime; + } } - public function setName(string $name): self { - if(empty($name)) - throw new InvalidArgumentException('$name may not be empty.'); - $this->name = $name; - return $this; + public int $pieceLength = 0 { + get => $this->pieceLength; + set(int $pieceLength) { + if($pieceLength < 1) + throw new InvalidArgumentException('$pieceLength is not a valid piece length.'); + + $this->pieceLength = $pieceLength; + } } - public function setCreatedTime(int $created): self { - if($created < 0 || $created > 0x7FFFFFFF) - throw new InvalidArgumentException('$created is not a valid timestamp.'); - $this->created = $created; - return $this; - } + public bool $private = false; + public string $comment = ''; - public function setPieceLength(int $pieceLength): self { - if($pieceLength < 1) - throw new InvalidArgumentException('$pieceLength is not a valid piece length.'); - $this->pieceLength = $pieceLength; - return $this; - } + private array $files = []; - public function setPrivate(bool $private): self { - $this->isPrivate = $private; - return $this; - } - - public function setComment(string $comment): self { - $this->comment = $comment; - return $this; - } - - public function addFile(string|array $path, int $length): self { + public function addFile(string|array $path, int $length): void { if(is_array($path)) $path = implode('/', $path); @@ -74,17 +62,15 @@ class TorrentBuilder { 'length' => $length, 'path' => explode('/', $path), ]; - - return $this; } - public function addPiece(string $hash): self { + private array $pieces = []; + + public function addPiece(string $hash): void { if(strlen($hash) !== 20) throw new InvalidArgumentException('$hash is not a valid piece hash.'); $this->pieces[] = $hash; - - return $this; } public function calculateInfoHash(): string { @@ -95,31 +81,27 @@ class TorrentBuilder { 'pieces' => implode($this->pieces), ]; - if(!empty($this->isPrivate)) + if(!empty($this->private)) $info['private'] = 1; return hash('sha1', Bencode::encode($info), true); } public function create(DbConnection $dbConn, TorrentsContext $torrentsCtx): string { - $torrents = $torrentsCtx->getTorrents(); - $pieces = $torrentsCtx->getPieces(); - $files = $torrentsCtx->getFiles(); - $tx = $dbConn->beginTransaction(); try { $infoHash = $this->calculateInfoHash(); - $torrentId = $torrents->createTorrent( - $this->userId, $infoHash, $this->name, $this->created, - $this->pieceLength, $this->isPrivate, $this->comment + $torrentId = $torrentsCtx->torrents->createTorrent( + $this->userId, $infoHash, $this->name, $this->createdTime, + $this->pieceLength, $this->private, $this->comment ); foreach($this->files as $file) - $files->createFile($torrentId, $file['length'], $file['path']); + $torrentsCtx->files->createFile($torrentId, $file['length'], $file['path']); foreach($this->pieces as $piece) - $pieces->createPiece($torrentId, $piece); + $torrentsCtx->pieces->createPiece($torrentId, $piece); $tx->commit(); } catch(Exception $ex) { @@ -132,22 +114,22 @@ class TorrentBuilder { public static function import( TorrentInfo $torrent, - array $pieces, - array $files + iterable $pieces, + iterable $files ): self { $builder = new TorrentBuilder; - $builder->setUserId($torrent->getUserId()); - $builder->setName($torrent->getName()); - $builder->setPieceLength($torrent->getPieceLength()); - $builder->setPrivate($torrent->isPrivate()); - $builder->setCreatedTime($torrent->getCreatedTime()); - $builder->setComment($torrent->getComment()); + $builder->userId = $torrent->userId; + $builder->name = $torrent->name; + $builder->pieceLength = $torrent->pieceLength; + $builder->private = $torrent->private; + $builder->createdTime = $torrent->createdTime; + $builder->comment = $torrent->comment; foreach($pieces as $piece) - $builder->addPiece($piece->getHash()); + $builder->addPiece($piece->hash); foreach($files as $file) - $builder->addFile($file->getPath(), $file->getLength()); + $builder->addFile($file->path, $file->length); return $builder; } @@ -168,16 +150,15 @@ class TorrentBuilder { throw new InvalidArgumentException('info.piece length key missing.'); $builder = new TorrentBuilder; - $builder->setName($source['info']['name']); - $builder->setPieceLength($source['info']['piece length']); - $builder->setPrivate(!empty($source['info']['private'])); + $builder->name = $source['info']['name']; + $builder->pieceLength = $source['info']['piece length']; + $builder->private = !empty($source['info']['private']); - if(isset($source['creation date']) - && is_int($source['creation date'])) - $builder->setCreatedTime($source['creation date']); + if(isset($source['creation date']) && is_int($source['creation date'])) + $builder->createdTime = $source['creation date']; if(!empty($source['comment'])) - $builder->setComment($source['comment']); + $builder->comment = $source['comment']; foreach($source['info']['files'] as $file) { if(empty($file) diff --git a/src/Torrents/TorrentCreateRouting.php b/src/Torrents/TorrentCreateRoutes.php similarity index 66% rename from src/Torrents/TorrentCreateRouting.php rename to src/Torrents/TorrentCreateRoutes.php index cbf9e8d..09dc34e 100644 --- a/src/Torrents/TorrentCreateRouting.php +++ b/src/Torrents/TorrentCreateRoutes.php @@ -5,13 +5,14 @@ use Exception; use RuntimeException; use Index\CsrfToken; use Index\Db\DbConnection; -use Index\Http\Routing\{HttpGet,HttpMiddleware,HttpPost,RouteHandler,RouteHandlerTrait}; +use Index\Http\{FormHttpContent,HttpResponseBuilder,HttpRequest}; +use Index\Http\Routing\{HttpGet,HttpMiddleware,HttpPost,RouteHandler,RouteHandlerCommon}; use Index\Templating\TplEnvironment; use Seria\Auth\AuthInfo; use Seria\Users\UsersContext; -class TorrentCreateRouting implements RouteHandler { - use RouteHandlerTrait; +class TorrentCreateRoutes implements RouteHandler { + use RouteHandlerCommon; public function __construct( private DbConnection $dbConn, @@ -23,14 +24,14 @@ class TorrentCreateRouting implements RouteHandler { #[HttpMiddleware('/create')] public function checkAccess() { - if(!$this->authInfo->isLoggedIn()) + if(!$this->authInfo->loggedIn) return 403; - if(!$this->authInfo->getUserInfo()->canCreateTorrents()) + if(!$this->authInfo->userInfo->canCreateTorrents) return 403; } #[HttpGet('/create')] - public function getCreate($response, $request) { + public function getCreate(HttpResponseBuilder $response, HttpRequest $request) { $template = $this->templating->load('create'); if($request->hasParam('error')) @@ -46,26 +47,24 @@ class TorrentCreateRouting implements RouteHandler { } #[HttpPost('/create')] - public function postCreate($response, $request) { - if(!$request->isFormContent()) + public function postCreate(HttpResponseBuilder $response, HttpRequest $request) { + if(!($request->content instanceof FormHttpContent)) return 400; - $content = $request->getContent(); - if(!$content->hasUploadedFile('torrent')) { + if(!$request->content->hasUploadedFile('torrent')) { $response->redirect('/create?error=file'); return; } - if(!$this->csrfp->verifyToken((string)$content->getParam('_csrfp'))) { + if(!$this->csrfp->verifyToken((string)$request->content->getParam('_csrfp'))) { $response->redirect('/create?error=verify'); return; } - $torrent = $content->getUploadedFile('torrent'); + $torrent = $request->content->getUploadedFile('torrent'); - $error = $torrent->getErrorCode(); - if($error !== UPLOAD_ERR_OK) { - $response->redirect('/create?error=' . match($error) { + if($torrent->errorCode !== UPLOAD_ERR_OK) { + $response->redirect('/create?error=' . match($torrent->errorCode) { UPLOAD_ERR_NO_FILE => 'file', UPLOAD_ERR_INI_SIZE => 'size', UPLOAD_ERR_FORM_SIZE => 'size', @@ -74,16 +73,15 @@ class TorrentCreateRouting implements RouteHandler { return; } - $path = $torrent->getLocalFileName(); - if($path === null || !is_file($path)) { + if($torrent->localFileName === null || !is_file($torrent->localFileName)) { $response->redirect('/create?error=file'); return; } - $file = fopen($path, 'rb'); + $file = fopen($torrent->localFileName, 'rb'); try { $torrentBuilder = TorrentBuilder::decode($file); - $torrentBuilder->setUser($this->authInfo->getUserInfo()); + $torrentBuilder->userId = $this->authInfo->userInfo?->id; $torrentId = $torrentBuilder->create($this->dbConn, $this->torrentsCtx); } catch(Exception $ex) { $response->redirect('/create?error=format'); @@ -97,7 +95,7 @@ class TorrentCreateRouting implements RouteHandler { } #[HttpGet('/create.php')] - public function getCreatePHP($response, $request) { + public function getCreatePHP(HttpResponseBuilder $response, HttpRequest $request) { $response->redirect('/create', true); } } diff --git a/src/Torrents/TorrentFileInfo.php b/src/Torrents/TorrentFileInfo.php index 939fccc..e009b20 100644 --- a/src/Torrents/TorrentFileInfo.php +++ b/src/Torrents/TorrentFileInfo.php @@ -1,43 +1,30 @@ id = $result->getString(0); - $this->torrentId = $result->getString(1); - $this->length = $result->getInteger(2); - $this->path = $result->getString(3); - } - - public function getId(): string { - return $this->id; - } - - public function getTorrentId(): string { - return $this->torrentId; - } - - #[BencodeProperty('length')] - public function getLength(): int { - return $this->length; - } - - public function getPath(): string { - return $this->path; + public static function fromResult(DbResult $result): TorrentFileInfo { + return new TorrentFileInfo( + id: $result->getString(0), + torrentId: $result->getString(1), + length: $result->getInteger(2), + path: $result->getString(3), + ); } #[BencodeProperty('path')] - public function getPathArray(): array { - return explode('/', $this->path); + public array $pathArray { + get => explode('/', $this->path); } } diff --git a/src/Torrents/TorrentFiles.php b/src/Torrents/TorrentFiles.php index 94b9a39..11b341b 100644 --- a/src/Torrents/TorrentFiles.php +++ b/src/Torrents/TorrentFiles.php @@ -12,18 +12,12 @@ class TorrentFiles { $this->cache = new DbStatementCache($dbConn); } - public function getFiles(TorrentInfo|string $torrentInfo): array { + public function getFiles(TorrentInfo|string $torrentInfo): iterable { $stmt = $this->cache->get('SELECT file_id, torrent_id, file_length, file_path FROM ser_torrents_files WHERE torrent_id = ? ORDER BY file_id ASC'); - $stmt->addParameter(1, $torrentInfo instanceof TorrentInfo ? $torrentInfo->getId() : $torrentInfo); + $stmt->nextParameter($torrentInfo instanceof TorrentInfo ? $torrentInfo->id : $torrentInfo); $stmt->execute(); - $result = $stmt->getResult(); - $files = []; - - while($result->next()) - $files[] = new TorrentFileInfo($result); - - return $files; + return $stmt->getResultIterator(TorrentFileInfo::fromResult(...)); } public function createFile( @@ -32,16 +26,17 @@ class TorrentFiles { array|string $path ): void { $stmt = $this->cache->get('INSERT INTO ser_torrents_files (torrent_id, file_length, file_path) VALUES (?, ?, ?)'); - $stmt->addParameter(1, $torrentInfo instanceof TorrentInfo ? $torrentInfo->getId() : $torrentInfo); - $stmt->addParameter(2, $length); - $stmt->addParameter(3, is_array($path) ? implode('/', $path) : $path); + $stmt->nextParameter($torrentInfo instanceof TorrentInfo ? $torrentInfo->id : $torrentInfo); + $stmt->nextParameter($length); + $stmt->nextParameter(is_array($path) ? implode('/', $path) : $path); $stmt->execute(); } public function countTotalSize(TorrentInfo|string $torrentInfo): int { $stmt = $this->cache->get('SELECT SUM(file_length) FROM ser_torrents_files WHERE torrent_id = ?'); - $stmt->addParameter(1, $torrentInfo instanceof TorrentInfo ? $torrentInfo->getId() : $torrentInfo); + $stmt->nextParameter($torrentInfo instanceof TorrentInfo ? $torrentInfo->id : $torrentInfo); $stmt->execute(); + $result = $stmt->getResult(); return $result->next() ? $result->getInteger(0) : 0; } diff --git a/src/Torrents/TorrentInfo.php b/src/Torrents/TorrentInfo.php index aa7e22d..ef264f1 100644 --- a/src/Torrents/TorrentInfo.php +++ b/src/Torrents/TorrentInfo.php @@ -3,80 +3,36 @@ namespace Seria\Torrents; use Index\Db\DbResult; -readonly class TorrentInfo { - private string $id; - private ?string $userId; - private string $infoHash; - private int $active; - private string $name; - private int $created; - private ?int $approved; - private int $pieceLength; - private int $private; - private string $comment; +class TorrentInfo { + public function __construct( + public private(set) string $id, + public private(set) ?string $userId, + public private(set) string $infoHash, + public private(set) bool $active, // i don't think anything uses this field + public private(set) string $name, + public private(set) int $createdTime, + public private(set) ?int $approvedTime, + public private(set) int $pieceLength, + public private(set) bool $private, + public private(set) string $comment, + ) {} - public function __construct(DbResult $result) { - $this->id = $result->getString(0); - $this->userId = $result->isNull(1) ? null : $result->getString(1); - $this->infoHash = $result->getString(2); - $this->active = $result->getInteger(3); // i don't think anything uses this field - $this->name = $result->getString(4); - $this->created = $result->getInteger(5); - $this->approved = $result->isNull(6) ? null : $result->getInteger(6); - $this->pieceLength = $result->getInteger(7); - $this->private = $result->getInteger(8); - $this->comment = $result->getString(9); + public static function fromResult(DbResult $result): TorrentInfo { + return new TorrentInfo( + id: $result->getString(0), + userId: $result->getStringOrNull(1), + infoHash: $result->getString(2), + active: $result->getBoolean(3), + name: $result->getString(4), + createdTime: $result->getInteger(5), + approvedTime: $result->getIntegerOrNull(6), + pieceLength: $result->getInteger(7), + private: $result->getBoolean(8), + comment: $result->getString(9), + ); } - public function getId(): string { - return $this->id; - } - - public function hasUser(): bool { - return $this->userId !== null; - } - - public function getUserId(): ?string { - return $this->userId; - } - - public function getHash(): string { - return $this->infoHash; - } - - public function isActive(): bool { - return $this->active !== 0; - } - - public function getName(): string { - return $this->name; - } - - public function getCreatedTime(): int { - return $this->created; - } - - public function getApprovedTime(): ?int { - return $this->approved; - } - - public function isApproved(): bool { - return $this->approved !== null; - } - - public function getPieceLength(): int { - return $this->pieceLength; - } - - public function isPrivate(): bool { - return $this->private !== 0; - } - - public function hasComment(): bool { - return $this->comment !== ''; - } - - public function getComment(): string { - return $this->comment; + public bool $approved { + get => $this->approvedTime !== null; } } diff --git a/src/Torrents/TorrentInfoRouting.php b/src/Torrents/TorrentInfoRoutes.php similarity index 53% rename from src/Torrents/TorrentInfoRouting.php rename to src/Torrents/TorrentInfoRoutes.php index b5a64bb..67edb8e 100644 --- a/src/Torrents/TorrentInfoRouting.php +++ b/src/Torrents/TorrentInfoRoutes.php @@ -4,13 +4,14 @@ namespace Seria\Torrents; use RuntimeException; use Index\CsrfToken; use Index\Config\Config; -use Index\Http\Routing\{HttpGet,HttpMiddleware,HttpPost,RouteHandler,RouteHandlerTrait}; +use Index\Http\{FormHttpContent,HttpResponseBuilder,HttpRequest}; +use Index\Http\Routing\{HttpGet,HttpMiddleware,HttpPost,RouteHandler,RouteHandlerCommon}; use Index\Templating\TplEnvironment; use Seria\Auth\AuthInfo; use Seria\Users\UsersContext; -class TorrentInfoRouting implements RouteHandler { - use RouteHandlerTrait; +class TorrentInfoRoutes implements RouteHandler { + use RouteHandlerCommon; private ?TorrentInfo $torrentInfo = null; @@ -24,17 +25,17 @@ class TorrentInfoRouting implements RouteHandler { ) {} #[HttpGet('/download/([0-9]+)')] - public function getDownload($response, $request, string $torrentId) { + public function getDownload(HttpResponseBuilder $response, HttpRequest $request, string $torrentId) { try { - $torrentInfo = $this->torrentsCtx->getTorrents()->getTorrent($torrentId); + $torrentInfo = $this->torrentsCtx->torrents->getTorrent($torrentId); } catch(RuntimeException $ex) { - $response->setStatusCode(404); + $response->statusCode = 404; return 'Download not found.'; } - $canDownload = $this->torrentsCtx->canDownloadTorrent($torrentInfo, $this->authInfo->getUserInfo()); + $canDownload = $this->torrentsCtx->canDownloadTorrent($torrentInfo, $this->authInfo->userInfo); if($canDownload !== '') { - $response->setStatusCode(403); + $response->statusCode = 403; return match($canDownload) { 'inactive' => 'This download is inactive.', @@ -45,33 +46,32 @@ class TorrentInfoRouting implements RouteHandler { } $trackerUrl = ''; - if($this->authInfo->isLoggedIn()) { - $userInfo = $this->authInfo->getUserInfo(); - $passKey = $userInfo->hasPassKey() - ? $userInfo->getPassKey() - : $this->usersCtx->getUsers()->updatePassKey($userInfo); + if($this->authInfo->loggedIn) { + $passKey = $this->authInfo->userInfo->passKey !== null + ? $this->authInfo->userInfo->passKey + : $this->usersCtx->users->updatePassKey($this->authInfo->userInfo); $trackerUrl = sprintf($this->config->getString('url:user'), $passKey); } else $trackerUrl = $this->config->getString('url:anon'); - $response->setContentType('application/x-bittorrent'); - $response->setFileName(htmlspecialchars($torrentInfo->getName()) . '.torrent'); + $response->setContentType('application/x-bittorrent; charset=us-ascii'); + $response->setFileName(htmlspecialchars($torrentInfo->name) . '.torrent'); return $this->torrentsCtx->encodeTorrent($torrentInfo, $trackerUrl); } private function getTorrentInfo(string $torrentId): int { - if($this->torrentInfo?->getId() === $torrentId) + if($this->torrentInfo?->id === $torrentId) return 0; try { - $this->torrentInfo = $this->torrentsCtx->getTorrents()->getTorrent($torrentId); + $this->torrentInfo = $this->torrentsCtx->torrents->getTorrent($torrentId); } catch(RuntimeException $ex) { return 404; } - $canDownload = $this->torrentsCtx->canDownloadTorrent($this->torrentInfo, $this->authInfo->getUserInfo()); + $canDownload = $this->torrentsCtx->canDownloadTorrent($this->torrentInfo, $this->authInfo->userInfo); if($canDownload !== '') return 403; @@ -79,22 +79,20 @@ class TorrentInfoRouting implements RouteHandler { } #[HttpGet('/info/([0-9]+)')] - public function getInfo($response, $request, string $torrentId) { + public function getInfo(HttpResponseBuilder $response, HttpRequest $request, string $torrentId) { $error = $this->getTorrentInfo($torrentId); - if($error > 0) return $error; + if($error > 0) + return $error; - if($this->torrentInfo->hasUser()) { - $users = $this->usersCtx->getUsers(); - $userInfo = $users->getUser($this->torrentInfo->getUserId(), 'id'); - } else $userInfo = null; + $userInfo = $this->torrentInfo->userId !== null + ? $this->usersCtx->users->getUser($this->torrentInfo->userId, 'id') + : null; - $peers = $this->torrentsCtx->getPeers(); - $completePeers = $peers->countCompletePeers($this->torrentInfo); - $incompletePeers = $peers->countIncompletePeers($this->torrentInfo); + $completePeers = $this->torrentsCtx->peers->countCompletePeers($this->torrentInfo); + $incompletePeers = $this->torrentsCtx->peers->countIncompletePeers($this->torrentInfo); - $files = $this->torrentsCtx->getFiles(); - $totalFileSize = $files->countTotalSize($this->torrentInfo); - $fileList = $files->getFiles($this->torrentInfo); + $totalFileSize = $this->torrentsCtx->files->countTotalSize($this->torrentInfo); + $fileList = $this->torrentsCtx->files->getFiles($this->torrentInfo); return $this->templating->render('info', [ 'torrent_info' => $this->torrentInfo, @@ -102,22 +100,19 @@ class TorrentInfoRouting implements RouteHandler { 'torrent_total_size' => $totalFileSize, 'torrent_complete_peers' => $completePeers, 'torrent_incomplete_peers' => $incompletePeers, - 'torrent_files' => $fileList, + 'torrent_files' => iterator_to_array($fileList, false), ]); } #[HttpMiddleware('/info/([0-9]+)/rehash')] #[HttpMiddleware('/info/([0-9]+)/approve')] #[HttpMiddleware('/info/([0-9]+)/deny')] - public function verifyRequest($response, $request, string $torrentId) { - if(!$this->authInfo->isLoggedIn()) + public function verifyRequest(HttpResponseBuilder $response, HttpRequest $request, string $torrentId) { + if(!$this->authInfo->loggedIn) return 401; - - if(!$request->isFormContent()) + if(!($request->content instanceof FormHttpContent)) return 400; - - $content = $request->getContent(); - if(!$this->csrfp->verifyToken((string)$content->getParam('_csrfp'))) + if(!$this->csrfp->verifyToken((string)$request->content->getParam('_csrfp'))) return 403; $error = $this->getTorrentInfo($torrentId); @@ -125,20 +120,20 @@ class TorrentInfoRouting implements RouteHandler { } #[HttpPost('/info/([0-9]+)/rehash')] - public function postRehash($response, $request, string $torrentId) { + public function postRehash(HttpResponseBuilder $response, HttpRequest $request, string $torrentId) { $error = $this->getTorrentInfo($torrentId); if($error > 0) return $error; - if(!$this->authInfo->getUserInfo()->canRecalculateInfoHash()) + if(!$this->authInfo->userInfo->canRecalculateInfoHash) return 403; $builder = TorrentBuilder::import( $this->torrentInfo, - $this->torrentsCtx->getPieces()->getPieces($this->torrentInfo), - $this->torrentsCtx->getFiles()->getFiles($this->torrentInfo) + $this->torrentsCtx->pieces->getPieces($this->torrentInfo), + $this->torrentsCtx->files->getFiles($this->torrentInfo) ); $infoHash = $builder->calculateInfoHash(); - $this->torrentsCtx->getTorrents()->updateTorrentInfoHash($this->torrentInfo, $infoHash); + $this->torrentsCtx->torrents->updateTorrentInfoHash($this->torrentInfo, $infoHash); return [ 'hash' => base64_encode($infoHash), @@ -146,33 +141,34 @@ class TorrentInfoRouting implements RouteHandler { } #[HttpPost('/info/([0-9]+)/approve')] - public function postApprove($response, $request, string $torrentId) { + public function postApprove(HttpResponseBuilder $response, HttpRequest $request, string $torrentId) { $error = $this->getTorrentInfo($torrentId); - if($error > 0) return $error; + if($error > 0) + return $error; - if(!$this->authInfo->getUserInfo()->canApproveTorrents()) + if(!$this->authInfo->userInfo->canApproveTorrents) return 403; - $this->torrentsCtx->getTorrents()->approveTorrent($this->torrentInfo); + $this->torrentsCtx->torrents->approveTorrent($this->torrentInfo); return 204; } #[HttpPost('/info/([0-9]+)/deny')] - public function postDeny($response, $request, string $torrentId) { + public function postDeny(HttpResponseBuilder $response, HttpRequest $request, string $torrentId) { $error = $this->getTorrentInfo($torrentId); if($error > 0) return $error; - if(!$this->authInfo->getUserInfo()->canApproveTorrents()) + if(!$this->authInfo->userInfo->canApproveTorrents) return 403; - $this->torrentsCtx->getTorrents()->deleteTorrent($this->torrentInfo); + $this->torrentsCtx->torrents->deleteTorrent($this->torrentInfo); return 204; } #[HttpGet('/info.php')] - public function getInfoPHP($response, $request) { + public function getInfoPHP(HttpResponseBuilder $response, HttpRequest $request) { $torrentId = (int)$request->getParam('id', FILTER_SANITIZE_NUMBER_INT); if($torrentId < 1) return 404; @@ -181,7 +177,7 @@ class TorrentInfoRouting implements RouteHandler { } #[HttpGet('/download.php')] - public function getDownloadPHP($response, $request) { + public function getDownloadPHP(HttpResponseBuilder $response, HttpRequest $request) { $torrentId = (int)$request->getParam('id', FILTER_SANITIZE_NUMBER_INT); if($torrentId < 1) return 404; diff --git a/src/Torrents/TorrentListRouting.php b/src/Torrents/TorrentListRoutes.php similarity index 57% rename from src/Torrents/TorrentListRouting.php rename to src/Torrents/TorrentListRoutes.php index 2318c83..7896fc9 100644 --- a/src/Torrents/TorrentListRouting.php +++ b/src/Torrents/TorrentListRoutes.php @@ -2,13 +2,15 @@ namespace Seria\Torrents; use RuntimeException; -use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerTrait}; +use Index\XArray; +use Index\Http\{HttpResponseBuilder,HttpRequest}; +use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerCommon}; use Index\Templating\TplEnvironment; use Seria\Auth\AuthInfo; use Seria\Users\UsersContext; -class TorrentListRouting implements RouteHandler { - use RouteHandlerTrait; +class TorrentListRoutes implements RouteHandler { + use RouteHandlerCommon; public function __construct( private AuthInfo $authInfo, @@ -18,15 +20,12 @@ class TorrentListRouting implements RouteHandler { ) {} #[HttpGet('/available')] - public function getAvailable($response, $request) { - $users = $this->usersCtx->getUsers(); - $peers = $this->torrentsCtx->getPeers(); - + public function getAvailable(HttpResponseBuilder $response, HttpRequest $request) { if($request->hasParam('name')) { $userName = (string)$request->getParam('name'); try { - $userInfo = $users->getUser($userName, 'name'); + $userInfo = $this->usersCtx->users->getUser($userName, 'name'); } catch(RuntimeException $ex) { return 404; } @@ -34,7 +33,7 @@ class TorrentListRouting implements RouteHandler { $url = '/available?'; if($userInfo !== null) - $url .= 'name=' . $userInfo->getName() . '&'; + $url .= 'name=' . $userInfo->name . '&'; $startAt = -1; $take = 20; @@ -42,21 +41,18 @@ class TorrentListRouting implements RouteHandler { if($request->hasParam('start')) $startAt = (int)$request->getParam('start', FILTER_SANITIZE_NUMBER_INT); - $torrents = []; - $torrentInfos = $this->torrentsCtx->getTorrents()->getTorrents( - public: $this->authInfo->isLoggedIn() ? null : true, + $torrents = XArray::select($this->torrentsCtx->torrents->getTorrents( + public: $this->authInfo->loggedIn ? null : true, approved: true, userInfo: $userInfo, startAt: $startAt, take: $take - ); - foreach($torrentInfos as $torrentInfo) - $torrents[] = [ - 'info' => $torrentInfo, - 'user' => $torrentInfo->hasUser() ? $users->getUser($torrentInfo->getUserId(), 'id') : null, - 'complete_peers' => $peers->countCompletePeers($torrentInfo), - 'incomplete_peers' => $peers->countIncompletePeers($torrentInfo), - ]; + ), fn($torrentInfo) => [ + 'info' => $torrentInfo, + 'user' => $torrentInfo->userId !== null ? $this->usersCtx->users->getUser($torrentInfo->userId, 'id') : null, + 'complete_peers' => $this->torrentsCtx->peers->countCompletePeers($torrentInfo), + 'incomplete_peers' => $this->torrentsCtx->peers->countIncompletePeers($torrentInfo), + ]); return $this->templating->render('available', [ 'page_url' => $url, @@ -66,15 +62,12 @@ class TorrentListRouting implements RouteHandler { } #[HttpGet('/pending')] - public function getPending($response, $request) { - if(!$this->authInfo->isLoggedIn()) + public function getPending(HttpResponseBuilder $response, HttpRequest $request) { + if(!$this->authInfo->loggedIn) return 403; - if(!$this->authInfo->getUserInfo()->canApproveTorrents()) + if(!$this->authInfo->userInfo->canApproveTorrents) return 403; - $users = $this->usersCtx->getUsers(); - $peers = $this->torrentsCtx->getPeers(); - $url = '/pending?'; $startAt = -1; $take = 20; @@ -82,19 +75,16 @@ class TorrentListRouting implements RouteHandler { if($request->hasParam('start')) $startAt = (int)$request->getParam('start', FILTER_SANITIZE_NUMBER_INT); - $torrents = []; - $torrentInfos = $this->torrentsCtx->getTorrents()->getTorrents( + $torrents = XArray::select($this->torrentsCtx->torrents->getTorrents( approved: false, startAt: $startAt, take: $take - ); - foreach($torrentInfos as $torrentInfo) - $torrents[] = [ - 'info' => $torrentInfo, - 'user' => $torrentInfo->hasUser() ? $users->getUser($torrentInfo->getUserId(), 'id') : null, - 'complete_peers' => $peers->countCompletePeers($torrentInfo), - 'incomplete_peers' => $peers->countIncompletePeers($torrentInfo), - ]; + ), fn($torrentInfo) => [ + 'info' => $torrentInfo, + 'user' => $torrentInfo->userId !== null ? $this->usersCtx->users->getUser($torrentInfo->userId, 'id') : null, + 'complete_peers' => $this->torrentsCtx->peers->countCompletePeers($torrentInfo), + 'incomplete_peers' => $this->torrentsCtx->peers->countIncompletePeers($torrentInfo), + ]); return $this->templating->render('pending', [ 'page_url' => $url, @@ -103,7 +93,7 @@ class TorrentListRouting implements RouteHandler { } #[HttpGet('/available.php')] - public function getAvailablePHP($response, $request): void { + public function getAvailablePHP(HttpResponseBuilder $response, HttpRequest $request): void { $query = []; $name = (string)$request->getParam('name'); @@ -121,7 +111,7 @@ class TorrentListRouting implements RouteHandler { } #[HttpGet('/pending.php')] - public function getPendingPHP($response, $request): void { + public function getPendingPHP(HttpResponseBuilder $response, HttpRequest $request): void { $query = []; $start = (int)$request->getParam('start', FILTER_SANITIZE_NUMBER_INT); diff --git a/src/Torrents/TorrentPeerInfo.php b/src/Torrents/TorrentPeerInfo.php index b7f27c7..2a18653 100644 --- a/src/Torrents/TorrentPeerInfo.php +++ b/src/Torrents/TorrentPeerInfo.php @@ -4,106 +4,58 @@ namespace Seria\Torrents; use Index\Db\DbResult; use Seria\Users\UserInfo; -readonly class TorrentPeerInfo { - private string $id; - private string $torrentId; - private ?string $userId; - private string $address; - private int $port; - private int $updated; - private int $expires; - private string $agent; - private string $key; - private int $bytesUploaded; - private int $bytesDownloaded; - private int $bytesLeft; +class TorrentPeerInfo { + public function __construct( + public private(set) string $id, + public private(set) string $torrentId, + public private(set) ?string $userId, + public private(set) string $address, + public private(set) int $port, + public private(set) int $updatedTime, + public private(set) int $expiresTime, + public private(set) string $agent, + #[\SensitiveParameter] private string $key, + public private(set) int $bytesUploaded, + public private(set) int $bytesDownloaded, + public private(set) int $bytesLeft, + ) {} - public function __construct(DbResult $result) { - $this->id = $result->getString(0); - $this->torrentId = $result->getString(1); - $this->userId = $result->isNull(2) ? null : $result->getString(2); - $this->address = $result->getString(3); - $this->port = $result->getInteger(4); - $this->updated = $result->getInteger(5); - $this->expires = $result->getInteger(6); - $this->agent = $result->getString(7); - $this->key = $result->getString(8); - $this->bytesUploaded = $result->getInteger(9); - $this->bytesDownloaded = $result->getInteger(10); - $this->bytesLeft = $result->getInteger(11); + public static function fromResult(DbResult $result): TorrentPeerInfo { + return new TorrentPeerInfo( + id: $result->getString(0), + torrentId: $result->getString(1), + userId: $result->getStringOrNull(2), + address: $result->getString(3), + port: $result->getInteger(4), + updatedTime: $result->getInteger(5), + expiresTime: $result->getInteger(6), + agent: $result->getString(7), + key: $result->getString(8), + bytesUploaded: $result->getInteger(9), + bytesDownloaded: $result->getInteger(10), + bytesLeft: $result->getInteger(11), + ); } - public function getId(): string { - return $this->id; + public string $addressRaw { + get => inet_pton($this->address); } - public function getTorrentId(): string { - return $this->torrentId; + public bool $seed { + get => $this->bytesLeft <= 0; } - public function getUserId(): ?string { - return $this->userId; - } - - public function hasUserId(): bool { - return $this->userId !== null; + public bool $leech { + get => $this->bytesLeft > 0; } public function verifyUser(?UserInfo $userInfo): bool { return $this->userId === null ? $userInfo === null - : $this->userId === $userInfo->getId(); - } - - public function getAddress(): string { - return $this->address; - } - - public function getAddressRaw(): string { - return inet_pton($this->address); - } - - public function getPort(): int { - return $this->port; - } - - public function getUpdatedTime(): int { - return $this->updated; - } - - public function getExpiresTime(): int { - return $this->expires; - } - - public function getAgent(): string { - return $this->agent; - } - - public function getKey(): string { - return $this->key; - } - - public function getBytesUploaded(): int { - return $this->bytesUploaded; - } - - public function getBytesDownloaded(): int { - return $this->bytesDownloaded; - } - - public function getBytesRemaining(): int { - return $this->bytesLeft; - } - - public function isSeed(): bool { - return $this->bytesLeft <= 0; - } - - public function isLeech(): bool { - return $this->bytesLeft > 0; + : $this->userId === $userInfo->id; } public function verifyKey(string $key): bool { - return hash_equals($this->getKey(), $key); + return hash_equals($this->key, $key); } } diff --git a/src/Torrents/TorrentPeers.php b/src/Torrents/TorrentPeers.php index 9b046a6..e585a25 100644 --- a/src/Torrents/TorrentPeers.php +++ b/src/Torrents/TorrentPeers.php @@ -17,31 +17,25 @@ class TorrentPeers { $this->dbConn->execute('DELETE FROM ser_torrents_peers WHERE peer_expires < NOW()'); } - public function getPeers(TorrentInfo|string $torrentInfo): array { + public function getPeers(TorrentInfo|string $torrentInfo): iterable { $stmt = $this->cache->get('SELECT peer_id, torrent_id, user_id, INET6_NTOA(peer_address), peer_port, UNIX_TIMESTAMP(peer_updated), UNIX_TIMESTAMP(peer_expires), peer_agent, peer_key, peer_uploaded, peer_downloaded, peer_left FROM ser_torrents_peers WHERE torrent_id = ?'); - $stmt->addParameter(1, $torrentInfo instanceof TorrentInfo ? $torrentInfo->getId() : $torrentInfo); + $stmt->nextParameter($torrentInfo instanceof TorrentInfo ? $torrentInfo->id : $torrentInfo); $stmt->execute(); - $peers = []; - $result = $stmt->getResult(); - - while($result->next()) - $peers[] = new TorrentPeerInfo($result); - - return $peers; + return $stmt->getResultIterator(TorrentPeerInfo::fromResult(...)); } public function getPeer(TorrentInfo|string $torrentInfo, string $peerId): ?TorrentPeerInfo { $stmt = $this->cache->get('SELECT peer_id, torrent_id, user_id, INET6_NTOA(peer_address), peer_port, UNIX_TIMESTAMP(peer_updated), UNIX_TIMESTAMP(peer_expires), peer_agent, peer_key, peer_uploaded, peer_downloaded, peer_left FROM ser_torrents_peers WHERE torrent_id = ? AND peer_id = ?'); - $stmt->addParameter(1, $torrentInfo instanceof TorrentInfo ? $torrentInfo->getId() : $torrentInfo); - $stmt->addParameter(2, $peerId); + $stmt->nextParameter($torrentInfo instanceof TorrentInfo ? $torrentInfo->id : $torrentInfo); + $stmt->nextParameter($peerId); $stmt->execute(); $result = $stmt->getResult(); if(!$result->next()) return null; - return new TorrentPeerInfo($result); + return TorrentPeerInfo::fromResult($result); } public function createPeer( @@ -58,20 +52,24 @@ class TorrentPeers { int $bytesRemaining ): TorrentPeerInfo { $stmt = $this->cache->get('INSERT INTO ser_torrents_peers (torrent_id, user_id, peer_id, peer_address, peer_port, peer_updated, peer_expires, peer_agent, peer_key, peer_uploaded, peer_downloaded, peer_left) VALUES (?, ?, ?, INET6_ATON(?), ?, NOW(), NOW() + INTERVAL ? SECOND, ?, ?, ?, ?, ?)'); - $stmt->addParameter(1, $torrentInfo instanceof TorrentInfo ? $torrentInfo->getId() : $torrentInfo); - $stmt->addParameter(2, $userInfo === null ? null : ($userInfo instanceof UserInfo ? $userInfo->getId() : $userInfo)); - $stmt->addParameter(3, $peerId); - $stmt->addParameter(4, $remoteAddr); - $stmt->addParameter(5, $remotePort); - $stmt->addParameter(6, $interval); - $stmt->addParameter(7, $peerAgent); - $stmt->addParameter(8, $peerKey); - $stmt->addParameter(9, $bytesUploaded); - $stmt->addParameter(10, $bytesDownloaded); - $stmt->addParameter(11, $bytesRemaining); + $stmt->nextParameter($torrentInfo instanceof TorrentInfo ? $torrentInfo->id : $torrentInfo); + $stmt->nextParameter($userInfo === null ? null : ($userInfo instanceof UserInfo ? $userInfo->id : $userInfo)); + $stmt->nextParameter($peerId); + $stmt->nextParameter($remoteAddr); + $stmt->nextParameter($remotePort); + $stmt->nextParameter($interval); + $stmt->nextParameter($peerAgent); + $stmt->nextParameter($peerKey); + $stmt->nextParameter($bytesUploaded); + $stmt->nextParameter($bytesDownloaded); + $stmt->nextParameter($bytesRemaining); $stmt->execute(); - return $this->getPeer($torrentInfo, $peerId) ?? throw new RuntimeException('Failed to record peer information.'); + $peerInfo = $this->getPeer($torrentInfo, $peerId); + if($peerInfo === null) + throw new RuntimeException('Failed to record peer information.'); + + return $peerInfo; } public function updatePeer( @@ -86,28 +84,28 @@ class TorrentPeers { int $bytesRemaining ): void { $stmt = $this->cache->get('UPDATE ser_torrents_peers SET peer_address = INET6_ATON(?), peer_port = ?, peer_updated = NOW(), peer_expires = NOW() + INTERVAL ? SECOND, peer_agent = ?, peer_uploaded = ?, peer_downloaded = ?, peer_left = ? WHERE torrent_id = ? AND peer_id = ?'); - $stmt->addParameter(1, $remoteAddr); - $stmt->addParameter(2, $remotePort); - $stmt->addParameter(3, $interval); - $stmt->addParameter(4, $peerAgent); - $stmt->addParameter(5, $bytesUploaded); - $stmt->addParameter(6, $bytesDownloaded); - $stmt->addParameter(7, $bytesRemaining); - $stmt->addParameter(8, $torrentInfo instanceof TorrentInfo ? $torrentInfo->getId() : $torrentInfo); - $stmt->addParameter(9, $peerInfo instanceof TorrentPeerInfo ? $peerInfo->getId() : $peerInfo); + $stmt->nextParameter($remoteAddr); + $stmt->nextParameter($remotePort); + $stmt->nextParameter($interval); + $stmt->nextParameter($peerAgent); + $stmt->nextParameter($bytesUploaded); + $stmt->nextParameter($bytesDownloaded); + $stmt->nextParameter($bytesRemaining); + $stmt->nextParameter($torrentInfo instanceof TorrentInfo ? $torrentInfo->id : $torrentInfo); + $stmt->nextParameter($peerInfo instanceof TorrentPeerInfo ? $peerInfo->id : $peerInfo); $stmt->execute(); } public function deletePeer(TorrentInfo|string $torrentInfo, TorrentPeerInfo|string $peerInfo): void { $stmt = $this->cache->get('DELETE FROM ser_torrents_peers WHERE torrent_id = ? AND peer_id = ?'); - $stmt->addParameter(1, $torrentInfo instanceof TorrentInfo ? $torrentInfo->getId() : $torrentInfo); - $stmt->addParameter(2, $peerInfo instanceof TorrentPeerInfo ? $peerInfo->getId() : $peerInfo); + $stmt->nextParameter($torrentInfo instanceof TorrentInfo ? $torrentInfo->id : $torrentInfo); + $stmt->nextParameter($peerInfo instanceof TorrentPeerInfo ? $peerInfo->id : $peerInfo); $stmt->execute(); } public function countIncompletePeers(TorrentInfo|string $torrentInfo): int { $stmt = $this->cache->get('SELECT COUNT(*) FROM ser_torrents_peers WHERE torrent_id = ? AND peer_left > 0'); - $stmt->addParameter(1, $torrentInfo instanceof TorrentInfo ? $torrentInfo->getId() : $torrentInfo); + $stmt->nextParameter($torrentInfo instanceof TorrentInfo ? $torrentInfo->id : $torrentInfo); $stmt->execute(); $result = $stmt->getResult(); return $result->next() ? $result->getInteger(0) : 0; @@ -115,7 +113,7 @@ class TorrentPeers { public function countCompletePeers(TorrentInfo|string $torrentInfo): int { $stmt = $this->cache->get('SELECT COUNT(*) FROM ser_torrents_peers WHERE torrent_id = ? AND peer_left <= 0'); - $stmt->addParameter(1, $torrentInfo instanceof TorrentInfo ? $torrentInfo->getId() : $torrentInfo); + $stmt->nextParameter($torrentInfo instanceof TorrentInfo ? $torrentInfo->id : $torrentInfo); $stmt->execute(); $result = $stmt->getResult(); return $result->next() ? $result->getInteger(0) : 0; @@ -123,7 +121,7 @@ class TorrentPeers { public function countUserDownloading(UserInfo|string $userInfo): int { $stmt = $this->cache->get('SELECT COUNT(*) FROM ser_torrents_peers WHERE user_id = ? AND peer_left > 0'); - $stmt->addParameter(1, $userInfo instanceof UserInfo ? $userInfo->getId() : $userInfo); + $stmt->nextParameter($userInfo instanceof UserInfo ? $userInfo->id : $userInfo); $stmt->execute(); $result = $stmt->getResult(); return $result->next() ? $result->getInteger(0) : 0; @@ -131,7 +129,7 @@ class TorrentPeers { public function countUserUploading(UserInfo|string $userInfo): int { $stmt = $this->cache->get('SELECT COUNT(*) FROM ser_torrents_peers WHERE user_id = ? AND peer_left <= 0'); - $stmt->addParameter(1, $userInfo instanceof UserInfo ? $userInfo->getId() : $userInfo); + $stmt->nextParameter($userInfo instanceof UserInfo ? $userInfo->id : $userInfo); $stmt->execute(); $result = $stmt->getResult(); return $result->next() ? $result->getInteger(0) : 0; diff --git a/src/Torrents/TorrentPieceInfo.php b/src/Torrents/TorrentPieceInfo.php index 5a02bbe..35ae42a 100644 --- a/src/Torrents/TorrentPieceInfo.php +++ b/src/Torrents/TorrentPieceInfo.php @@ -3,26 +3,18 @@ namespace Seria\Torrents; use Index\Db\DbResult; -readonly class TorrentPieceInfo { - private string $id; - private string $torrentId; - private string $hash; +class TorrentPieceInfo { + public function __construct( + public private(set) string $id, + public private(set) string $torrentId, + public private(set) string $hash, + ) {} - public function __construct(DbResult $result) { - $this->id = $result->getString(0); - $this->torrentId = $result->getString(1); - $this->hash = $result->getString(2); - } - - public function getId(): string { - return $this->id; - } - - public function getTorrentId(): string { - return $this->torrentId; - } - - public function getHash(): string { - return $this->hash; + public static function fromResult(DbResult $result): TorrentPieceInfo { + return new TorrentPieceInfo( + id: $result->getString(0), + torrentId: $result->getString(1), + hash: $result->getString(2), + ); } } diff --git a/src/Torrents/TorrentPieces.php b/src/Torrents/TorrentPieces.php index 1790a5b..fbeccc1 100644 --- a/src/Torrents/TorrentPieces.php +++ b/src/Torrents/TorrentPieces.php @@ -12,18 +12,11 @@ class TorrentPieces { $this->cache = new DbStatementCache($dbConn); } - public function getPieces(TorrentInfo|string $torrentInfo): array { + public function getPieces(TorrentInfo|string $torrentInfo): iterable { $stmt = $this->cache->get('SELECT piece_id, torrent_id, piece_hash FROM ser_torrents_pieces WHERE torrent_id = ? ORDER BY piece_id ASC'); - $stmt->addParameter(1, $torrentInfo instanceof TorrentInfo ? $torrentInfo->getId() : $torrentInfo); + $stmt->nextParameter($torrentInfo instanceof TorrentInfo ? $torrentInfo->id : $torrentInfo); $stmt->execute(); - - $result = $stmt->getResult(); - $pieces = []; - - while($result->next()) - $pieces[] = new TorrentPieceInfo($result); - - return $pieces; + return $stmt->getResultIterator(TorrentPieceInfo::fromResult(...)); } public function createPiece( @@ -31,8 +24,8 @@ class TorrentPieces { string $hash ): void { $stmt = $this->cache->get('INSERT INTO ser_torrents_pieces (torrent_id, piece_hash) VALUES (?, ?)'); - $stmt->addParameter(1, $torrentInfo instanceof TorrentInfo ? $torrentInfo->getId() : $torrentInfo); - $stmt->addParameter(2, $hash); + $stmt->nextParameter($torrentInfo instanceof TorrentInfo ? $torrentInfo->id : $torrentInfo); + $stmt->nextParameter($hash); $stmt->execute(); } } diff --git a/src/Torrents/Torrents.php b/src/Torrents/Torrents.php index ee14a0d..1bc2f60 100644 --- a/src/Torrents/Torrents.php +++ b/src/Torrents/Torrents.php @@ -9,8 +9,9 @@ use Seria\Users\UserInfo; class Torrents { private DbStatementCache $cache; - public function __construct(private DbConnection $dbConn) { - $this->dbConn = $dbConn; + public function __construct( + private DbConnection $dbConn + ) { $this->cache = new DbStatementCache($dbConn); } @@ -20,7 +21,7 @@ class Torrents { UserInfo|string|null $userInfo = null, int $startAt = -1, int $take = -1 - ): array { + ): iterable { $hasPublic = $public !== null; $hasApproved = $approved !== null; $hasUserInfo = $userInfo !== null; @@ -43,23 +44,16 @@ class Torrents { if($hasTake) $query .= ' LIMIT ?'; - $args = 0; $stmt = $this->cache->get($query); if($hasUserInfo) - $stmt->addParameter(++$args, $userInfo instanceof UserInfo ? $userInfo->getId() : $userInfo); + $stmt->nextParameter($userInfo instanceof UserInfo ? $userInfo->id : $userInfo); if($hasStartAt) - $stmt->addParameter(++$args, $startAt); + $stmt->nextParameter($startAt); if($hasTake) - $stmt->addParameter(++$args, $take); + $stmt->nextParameter($take); $stmt->execute(); - $torrents = []; - $result = $stmt->getResult(); - - while($result->next()) - $torrents[] = new TorrentInfo($result); - - return $torrents; + return $stmt->getResultIterator(TorrentInfo::fromResult(...)); } public const GET_TORRENT_ID = 0x01; @@ -101,19 +95,18 @@ class Torrents { if($selectHash) $query .= sprintf(' %s torrent_hash = ?', ++$args > 1 ? 'OR' : 'WHERE'); - $args = 0; $stmt = $this->cache->get($query); if($selectId) - $stmt->addParameter(++$args, $value); + $stmt->nextParameter($value); if($selectHash) - $stmt->addParameter(++$args, $value); + $stmt->nextParameter($value); $stmt->execute(); $result = $stmt->getResult(); if(!$result->next()) throw new RuntimeException('Download info not found.'); - return new TorrentInfo($result); + return TorrentInfo::fromResult($result); } public function createTorrent( @@ -126,34 +119,34 @@ class Torrents { string $comment ): string { $stmt = $this->cache->get('INSERT INTO ser_torrents (user_id, torrent_hash, torrent_name, torrent_created, torrent_piece_length, torrent_private, torrent_comment) VALUES (?, ?, ?, FROM_UNIXTIME(?), ?, ?, ?)'); - $stmt->addParameter(1, $userInfo === null ? null : ($userInfo instanceof UserInfo ? $userInfo->getId() : $userInfo)); - $stmt->addParameter(2, $infoHash); - $stmt->addParameter(3, $name); - $stmt->addParameter(4, $created); - $stmt->addParameter(5, $pieceLength); - $stmt->addParameter(6, $isPrivate ? 1 : 0); - $stmt->addParameter(7, $comment); + $stmt->nextParameter($userInfo === null ? null : ($userInfo instanceof UserInfo ? $userInfo->id : $userInfo)); + $stmt->nextParameter($infoHash); + $stmt->nextParameter($name); + $stmt->nextParameter($created); + $stmt->nextParameter($pieceLength); + $stmt->nextParameter($isPrivate ? 1 : 0); + $stmt->nextParameter($comment); $stmt->execute(); - return (string)$this->dbConn->getLastInsertId(); + return (string)$this->dbConn->lastInsertId; } public function updateTorrentInfoHash(TorrentInfo|string $torrentInfo, string $infoHash): void { $stmt = $this->cache->get('UPDATE ser_torrents SET torrent_hash = ? WHERE torrent_id = ?'); - $stmt->addParameter(1, $infoHash); - $stmt->addParameter(2, $torrentInfo instanceof TorrentInfo ? $torrentInfo->getId() : $torrentInfo); + $stmt->nextParameter($infoHash); + $stmt->nextParameter($torrentInfo instanceof TorrentInfo ? $torrentInfo->id : $torrentInfo); $stmt->execute(); } public function approveTorrent(TorrentInfo|string $torrentInfo): void { $stmt = $this->cache->get('UPDATE ser_torrents SET torrent_approved = COALESCE(torrent_approved, NOW()) WHERE torrent_id = ?'); - $stmt->addParameter(1, $torrentInfo instanceof TorrentInfo ? $torrentInfo->getId() : $torrentInfo); + $stmt->nextParameter($torrentInfo instanceof TorrentInfo ? $torrentInfo->id : $torrentInfo); $stmt->execute(); } public function deleteTorrent(TorrentInfo|string $torrentInfo): void { $stmt = $this->cache->get('DELETE FROM ser_torrents WHERE torrent_id = ?'); - $stmt->addParameter(1, $torrentInfo instanceof TorrentInfo ? $torrentInfo->getId() : $torrentInfo); + $stmt->nextParameter($torrentInfo instanceof TorrentInfo ? $torrentInfo->id : $torrentInfo); $stmt->execute(); } } diff --git a/src/Torrents/TorrentsContext.php b/src/Torrents/TorrentsContext.php index 3f71290..2742788 100644 --- a/src/Torrents/TorrentsContext.php +++ b/src/Torrents/TorrentsContext.php @@ -1,16 +1,17 @@ torrents = new Torrents($dbConn); @@ -19,33 +20,17 @@ class TorrentsContext { $this->pieces = new TorrentPieces($dbConn); } - public function getTorrents(): Torrents { - return $this->torrents; - } - - public function getFiles(): TorrentFiles { - return $this->files; - } - - public function getPeers(): TorrentPeers { - return $this->peers; - } - - public function getPieces(): TorrentPieces { - return $this->pieces; - } - public function canDownloadTorrent(TorrentInfo|string $torrentInfo, ?UserInfo $userInfo): string { if(is_string($torrentInfo)) $torrentInfo = $this->torrents->getTorrent($torrentInfo, 'id'); - if(!$torrentInfo->isActive()) + if(!$torrentInfo->active) return 'inactive'; - if($torrentInfo->isPrivate() && $userInfo === null) + if($torrentInfo->private && $userInfo === null) return 'private'; - if(!$torrentInfo->isApproved() && ($userInfo !== null && !$userInfo->canApproveTorrents() && $torrentInfo->getUserId() !== $userInfo->getId())) + if(!$torrentInfo->approved && ($userInfo !== null && !$userInfo->canApproveTorrents && $torrentInfo->userId !== $userInfo->id)) return 'pending'; return ''; @@ -55,32 +40,27 @@ class TorrentsContext { if(is_string($torrentInfo)) $torrentInfo = $this->torrents->getTorrent($torrentInfo, 'id'); - $pieces = ''; - $pieceInfos = $this->pieces->getPieces($torrentInfo); - foreach($pieceInfos as $piece) - $pieces .= $piece->getHash(); - // VERY IMPORTANT DETAIL: keep ordering identical to how it is in TorrentBuilder to not fuck up info hashes // this should really be combined somehow $info = [ - 'files' => $this->files->getFiles($torrentInfo), - 'name' => $torrentInfo->getName(), - 'piece length' => $torrentInfo->getPieceLength(), - 'pieces' => $pieces, + 'files' => iterator_to_array($this->files->getFiles($torrentInfo), false), + 'name' => $torrentInfo->name, + 'piece length' => $torrentInfo->pieceLength, + 'pieces' => implode('', XArray::select($this->pieces->getPieces($torrentInfo), fn($pieceInfo) => $pieceInfo->hash)), ]; - if($torrentInfo->isPrivate()) + if($torrentInfo->private) $info['private'] = 1; $data = [ 'announce' => $announceUrl, 'created by' => sprintf('Seria %s', GitInfo::version()), - 'creation date' => $torrentInfo->getCreatedTime(), + 'creation date' => $torrentInfo->createdTime, 'info' => $info, ]; - if($torrentInfo->hasComment()) - $data['comment'] = $torrentInfo->getComment(); + if(!empty($torrentInfo->comment)) + $data['comment'] = $torrentInfo->comment; return Bencode::encode($data); } diff --git a/src/Users/ProfileRoutes.php b/src/Users/ProfileRoutes.php index 090e4cf..235806e 100644 --- a/src/Users/ProfileRoutes.php +++ b/src/Users/ProfileRoutes.php @@ -2,13 +2,15 @@ namespace Seria\Users; use RuntimeException; -use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerTrait}; +use Index\XArray; +use Index\Http\{HttpResponseBuilder,HttpRequest}; +use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerCommon}; use Index\Templating\TplEnvironment; use Seria\Auth\AuthInfo; use Seria\Torrents\{TorrentsContext,TorrentInfo,TorrentPeerInfo}; class ProfileRoutes implements RouteHandler { - use RouteHandlerTrait; + use RouteHandlerCommon; public function __construct( private AuthInfo $authInfo, @@ -18,32 +20,24 @@ class ProfileRoutes implements RouteHandler { ) {} #[HttpGet('/profile/([a-zA-Z0-9\-_]+)')] - public function getProfile($response, $request, string $name) { - if(!$this->authInfo->isLoggedIn()) + public function getProfile(HttpResponseBuilder $response, HttpRequest $request, string $name) { + if(!$this->authInfo->loggedIn) return 403; - $users = $this->usersCtx->getUsers(); - $torrents = $this->torrentsCtx->getTorrents(); - $peers = $this->torrentsCtx->getPeers(); - try { - $userInfo = $users->getUser($name, 'name'); + $userInfo = $this->usersCtx->users->getUser($name, 'name'); } catch(RuntimeException $ex) { return 404; } - $submissions = []; - $torrentInfos = $torrents->getTorrents(approved: true, userInfo: $userInfo, take: 3); - foreach($torrentInfos as $torrentInfo) { - $submissions[] = [ - 'info' => $torrentInfo, - 'complete_peers' => $peers->countCompletePeers($torrentInfo), - 'incomplete_peers' => $peers->countIncompletePeers($torrentInfo), - ]; - } + $submissions = XArray::select($this->torrentsCtx->torrents->getTorrents(approved: true, userInfo: $userInfo, take: 3), fn($torrentInfo) => [ + 'info' => $torrentInfo, + 'complete_peers' => $this->torrentsCtx->peers->countCompletePeers($torrentInfo), + 'incomplete_peers' => $this->torrentsCtx->peers->countIncompletePeers($torrentInfo), + ]); - $uploading = $peers->countUserUploading($userInfo); - $downloading = $peers->countUserDownloading($userInfo); + $uploading = $this->torrentsCtx->peers->countUserUploading($userInfo); + $downloading = $this->torrentsCtx->peers->countUserDownloading($userInfo); return $this->templating->render('profile', [ 'profile_user' => $userInfo, @@ -54,14 +48,12 @@ class ProfileRoutes implements RouteHandler { } #[HttpGet('/profile/([a-zA-Z0-9\-_]+)/history')] - public function getHistory($response, $request, string $name) { - if(!$this->authInfo->isLoggedIn()) + public function getHistory(HttpResponseBuilder $response, HttpRequest $request, string $name) { + if(!$this->authInfo->loggedIn) return 403; - $users = $this->usersCtx->getUsers(); - try { - $userInfo = $users->getUser($name, 'name'); + $userInfo = $this->usersCtx->users->getUser($name, 'name'); } catch(RuntimeException $ex) { return 404; } @@ -72,15 +64,15 @@ class ProfileRoutes implements RouteHandler { } #[HttpGet('/profile.php')] - public function getProfilePHP($response, $request): void { + public function getProfilePHP(HttpResponseBuilder $response, HttpRequest $request): void { $response->redirect(sprintf('/profile/%s', (string)$request->getParam('name')), true); } #[HttpGet('/history.php')] - public function getHistoryPHP($response, $request) { + public function getHistoryPHP(HttpResponseBuilder $response, HttpRequest $request) { $userName = (string)$request->getParam('name'); - if($userName === '' && $this->authInfo->isLoggedIn()) - $userName = $this->authInfo->getUserName(); + if($userName === '' && $this->authInfo->loggedIn) + $userName = $this->authInfo->userName; if($userName === '') return 404; diff --git a/src/Users/SettingsRoutes.php b/src/Users/SettingsRoutes.php index de15480..d6a2e59 100644 --- a/src/Users/SettingsRoutes.php +++ b/src/Users/SettingsRoutes.php @@ -2,13 +2,14 @@ namespace Seria\Users; use Index\CsrfToken; -use Index\Http\Routing\{HttpGet,HttpMiddleware,HttpPost,RouteHandler,RouteHandlerTrait}; +use Index\Http\{FormHttpContent,HttpResponseBuilder,HttpRequest}; +use Index\Http\Routing\{HttpGet,HttpMiddleware,HttpPost,RouteHandler,RouteHandlerCommon}; use Index\Templating\TplEnvironment; use Seria\Auth\AuthInfo; use Seria\Users\UsersContext; class SettingsRoutes implements RouteHandler { - use RouteHandlerTrait; + use RouteHandlerCommon; public function __construct( private AuthInfo $authInfo, @@ -18,33 +19,31 @@ class SettingsRoutes implements RouteHandler { ) {} #[HttpMiddleware('/settings')] - public function checkLogin($response, $request) { - if(!$this->authInfo->isLoggedIn()) + public function checkLogin(HttpResponseBuilder $response, HttpRequest $request) { + if(!$this->authInfo->loggedIn) return 403; - if($request->getMethod() === 'POST') { - if(!$request->isFormContent()) + if($request->method === 'POST') { + if(!($request->content instanceof FormHttpContent)) return 400; - - $content = $request->getContent(); - if(!$this->csrfp->verifyToken((string)$content->getParam('_csrfp'))) + if(!$this->csrfp->verifyToken((string)$request->content->getParam('_csrfp'))) return 403; } } #[HttpGet('/settings')] - public function getIndex($response) { + public function getIndex(HttpResponseBuilder $response) { return $this->templating->render('settings'); } #[HttpPost('/settings/passkey')] - public function postPasskey($response) { - $this->usersCtx->getUsers()->updatePassKey($this->authInfo->getUserInfo()); + public function postPasskey(HttpResponseBuilder $response) { + $this->usersCtx->users->updatePassKey($this->authInfo->userInfo); $response->redirect('/settings'); } #[HttpGet('/settings.php')] - public function getSettingsPHP($response): void { + public function getSettingsPHP(HttpResponseBuilder $response): void { $response->redirect('/settings', true); } } diff --git a/src/Users/UserInfo.php b/src/Users/UserInfo.php index ddc495b..5015393 100644 --- a/src/Users/UserInfo.php +++ b/src/Users/UserInfo.php @@ -6,94 +6,55 @@ use Index\Colour\Colour; use Index\Db\DbResult; use Seria\Colours; -readonly class UserInfo { - private string $id; - private string $name; - private ?int $colour; - private int $rank; - private int $perms; - private ?string $passKey; - private int $bytesDownloaded; - private int $bytesUploaded; +class UserInfo { + public function __construct( + public private(set) string $id, + public private(set) string $name, + public private(set) ?int $colourRaw, + public private(set) int $rank, + public private(set) int $perms, + #[\SensitiveParameter] public private(set) ?string $passKey, + public private(set) int $bytesDownloaded, + public private(set) int $bytesUploaded, + ) {} - public function __construct(DbResult $result) { - $this->id = $result->getString(0); - $this->name = $result->getString(1); + public static function fromResult(DbResult $result): UserInfo { + $colour = $result->getIntegerOrNull(2); - $colour = $result->isNull(2) ? null : $result->getInteger(2); - $this->colour = $colour === null || ($colour & 0x40000000) ? null : $colour; - - $this->rank = $result->getInteger(3); - $this->perms = $result->getInteger(4); - $this->passKey = $result->isNull(5) ? null : $result->getString(5); - $this->bytesDownloaded = $result->getInteger(6); - $this->bytesUploaded = $result->getInteger(7); + return new UserInfo( + id: $result->getString(0), + name: $result->getString(1), + colourRaw: $colour === null || $colour & 0x40000000 ? null : $colour, + rank: $result->getInteger(3), + perms: $result->getInteger(4), + passKey: $result->getStringOrNull(5), + bytesDownloaded: $result->getInteger(6), + bytesUploaded: $result->getInteger(7), + ); } - public function getId(): string { - return $this->id; + public Colour $colour { + get => $this->colourRaw === null ? Colour::none() : Colours::cached($this->colourRaw); } - public function getName(): string { - return $this->name; + // The can* things should be in UsersContext as methods instead + public private(set) bool $canCreateTorrents = true; + + public bool $canApproveTorrents { + get => $this->id === '1'; } - public function hasColour(): bool { - return $this->colour !== null; + public bool $canRecalculateInfoHash { + get => $this->id === '1'; } - public function getColour(): Colour { - return $this->colour === null ? Colour::none() : Colours::cached($this->colour); - } + public float $ratio { + get { + $bd = $this->bytesDownloaded; + if($bd === 0) + return 0; - public function getColourRaw(): ?int { - return $this->colour; - } - - public function getRank(): int { - return $this->rank; - } - - public function getPermsRaw(): int { - return $this->perms; - } - - public function hasPassKey(): bool { - return $this->passKey !== null; - } - - public function getPassKey(): ?string { - return $this->passKey; - } - - public function getBytesDownloaded(): int { - return $this->bytesDownloaded; - } - - public function getBytesUploaded(): int { - return $this->bytesUploaded; - } - - public function isFlash(): bool { - return $this->id === '1'; - } - - public function canCreateTorrents(): bool { - return true; - } - - public function canApproveTorrents(): bool { - return $this->isFlash(); - } - - public function canRecalculateInfoHash(): bool { - return $this->isFlash(); - } - - public function calculateRatio(): float { - $bd = $this->getBytesDownloaded(); - if($bd === 0) - return 0; - return $this->getBytesUploaded() / $bd; + return $this->bytesUploaded / $bd; + } } } diff --git a/src/Users/Users.php b/src/Users/Users.php index adfc4a1..008862f 100644 --- a/src/Users/Users.php +++ b/src/Users/Users.php @@ -76,31 +76,30 @@ class Users { if($selectPassKey) $query .= sprintf(' %s user_pass_key = ?', ++$args > 1 ? 'OR' : 'WHERE'); - $args = 0; $stmt = $this->cache->get($query); if($selectId) - $stmt->addParameter(++$args, $value); + $stmt->nextParameter($value); if($selectName) - $stmt->addParameter(++$args, $value); + $stmt->nextParameter($value); if($selectPassKey) - $stmt->addParameter(++$args, $value); + $stmt->nextParameter($value); $stmt->execute(); $result = $stmt->getResult(); if(!$result->next()) throw new RuntimeException('User not found.'); - return new UserInfo($result); + return UserInfo::fromResult($result); } public function updatePassKey(UserInfo|string $userInfo): string { if($userInfo instanceof UserInfo) - $userInfo = $userInfo->getId(); + $userInfo = $userInfo->id; $passKey = self::generatePassKey(); $stmt = $this->cache->get('UPDATE ser_users SET user_pass_key = ? WHERE user_id = ?'); - $stmt->addParameter(1, $passKey); - $stmt->addParameter(2, $userInfo); + $stmt->nextParameter($passKey); + $stmt->nextParameter($userInfo); $stmt->execute(); return $passKey; @@ -108,12 +107,12 @@ class Users { public function incrementTransferStats(UserInfo|string $userInfo, int $bytesDownloaded, int $bytesUploaded): void { if($userInfo instanceof UserInfo) - $userInfo = $userInfo->getId(); + $userInfo = $userInfo->id; $stmt = $this->cache->get('UPDATE ser_users SET user_bytes_downloaded = user_bytes_downloaded + ?, user_bytes_uploaded = user_bytes_uploaded + ? WHERE user_id = ?'); - $stmt->addParameter(1, $bytesDownloaded); - $stmt->addParameter(2, $bytesUploaded); - $stmt->addParameter(3, $userInfo); + $stmt->nextParameter($bytesDownloaded); + $stmt->nextParameter($bytesUploaded); + $stmt->nextParameter($userInfo); $stmt->execute(); } } diff --git a/src/Users/UsersContext.php b/src/Users/UsersContext.php index 0687746..3654d08 100644 --- a/src/Users/UsersContext.php +++ b/src/Users/UsersContext.php @@ -4,13 +4,9 @@ namespace Seria\Users; use Index\Db\DbConnection; class UsersContext { - private Users $users; + public private(set) Users $users; public function __construct(DbConnection $dbConn) { $this->users = new Users($dbConn); } - - public function getUsers(): Users { - return $this->users; - } } diff --git a/templates/info.twig b/templates/info.twig index 704c426..3331321 100644 --- a/templates/info.twig +++ b/templates/info.twig @@ -8,7 +8,7 @@