PHP 8.4 and various fixes.
This commit is contained in:
parent
ada2baee17
commit
2ef4d6d66d
38 changed files with 639 additions and 983 deletions
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
131
composer.lock
generated
131
composer.lock
generated
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
namespace Seria;
|
||||
|
||||
use RuntimeException;
|
||||
use Flashii\{FlashiiClient,FlashiiUrls};
|
||||
use Flashii\Credentials\MisuzuCredentials;
|
||||
use Seria\Users\UserInfo;
|
||||
|
@ -9,19 +10,19 @@ require_once __DIR__ . '/../seria.php';
|
|||
|
||||
$authToken = (string)filter_input(INPUT_COOKIE, 'msz_auth');
|
||||
$flashii = new FlashiiClient('Seria', new MisuzuCredentials($authToken), new FlashiiUrls(
|
||||
$cfg->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
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = [];
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
<?php
|
||||
namespace Seria;
|
||||
|
||||
use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerTrait};
|
||||
use Index\Http\HttpResponseBuilder;
|
||||
use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerCommon};
|
||||
use Index\Templating\TplEnvironment;
|
||||
|
||||
class HomeRoutes implements RouteHandler {
|
||||
use RouteHandlerTrait;
|
||||
use RouteHandlerCommon;
|
||||
|
||||
public function __construct(
|
||||
private ?TplEnvironment $templating
|
||||
|
@ -17,7 +18,7 @@ class HomeRoutes implements RouteHandler {
|
|||
}
|
||||
|
||||
#[HttpGet('/index.php')]
|
||||
public function getIndexPHP($response): void {
|
||||
public function getIndexPHP(HttpResponseBuilder $response): void {
|
||||
$response->redirect('/', true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
]));
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -1,37 +1,29 @@
|
|||
<?php
|
||||
namespace Seria\Torrents;
|
||||
|
||||
use Index\Bencode\{BencodeProperty,BencodeSerializable,BencodeSerializableTrait};
|
||||
use Index\Bencode\{BencodeProperty,BencodeSerializable,BencodeSerializableCommon};
|
||||
|
||||
class AnnounceEmpty implements BencodeSerializable {
|
||||
use BencodeSerializableTrait;
|
||||
use BencodeSerializableCommon;
|
||||
|
||||
public function __construct(
|
||||
private bool $compactPeers
|
||||
) {}
|
||||
|
||||
#[BencodeProperty('interval')]
|
||||
public function getInterval(): int {
|
||||
return 0;
|
||||
}
|
||||
#[BencodeProperty]
|
||||
public private(set) int $interval = 0;
|
||||
|
||||
#[BencodeProperty('min interval')]
|
||||
public function getMinimumInterval(): int {
|
||||
return 0;
|
||||
}
|
||||
public private(set) int $minInterval = 0;
|
||||
|
||||
#[BencodeProperty('complete')]
|
||||
public function getComplete(): int {
|
||||
return 0;
|
||||
}
|
||||
#[BencodeProperty]
|
||||
public private(set) int $complete = 0;
|
||||
|
||||
#[BencodeProperty('incomplete')]
|
||||
public function getIncomplete(): int {
|
||||
return 0;
|
||||
}
|
||||
#[BencodeProperty]
|
||||
public private(set) int $incomplete = 0;
|
||||
|
||||
#[BencodeProperty('peers')]
|
||||
public function getIncomplete(): string|array {
|
||||
return $this->compactPeers ? '' : [];
|
||||
#[BencodeProperty]
|
||||
public string|array $peers {
|
||||
get => $this->compactPeers ? '' : [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
<?php
|
||||
namespace Seria\Torrents;
|
||||
|
||||
use Index\Bencode\{BencodeProperty,BencodeSerializable,BencodeSerializableTrait};
|
||||
use Index\Bencode\{BencodeProperty,BencodeSerializable,BencodeSerializableCommon};
|
||||
|
||||
class AnnounceFailure implements BencodeSerializable {
|
||||
use BencodeSerializableTrait;
|
||||
use BencodeSerializableCommon;
|
||||
|
||||
public function __construct(private string $reason) {}
|
||||
|
||||
#[BencodeProperty('failure reason')]
|
||||
public function getReason(): string {
|
||||
return $this->reason;
|
||||
}
|
||||
public function __construct(
|
||||
#[BencodeProperty('failure reason')]
|
||||
public private(set) string $reason
|
||||
) {}
|
||||
}
|
||||
|
|
|
@ -1,78 +1,48 @@
|
|||
<?php
|
||||
namespace Seria\Torrents;
|
||||
|
||||
use Index\Bencode\{BencodeProperty,BencodeSerializable,BencodeSerializableTrait};
|
||||
use Index\XArray;
|
||||
use Index\Bencode\{BencodeProperty,BencodeSerializable,BencodeSerializableCommon};
|
||||
|
||||
class AnnounceInfo implements BencodeSerializable {
|
||||
use BencodeSerializableTrait;
|
||||
use BencodeSerializableCommon;
|
||||
|
||||
// todo: keep these in a field in ser_torrents so we don't need this slightly filthy hack anymore
|
||||
// also useful for ipv6 peer support
|
||||
private int $completePeers = 0;
|
||||
private int $incompletePeers = 0;
|
||||
#[BencodeProperty]
|
||||
public private(set) int $complete = 0;
|
||||
|
||||
#[BencodeProperty]
|
||||
public private(set) int $incomplete = 0;
|
||||
|
||||
public function __construct(
|
||||
private array $peers,
|
||||
private int $interval,
|
||||
private int $minInterval,
|
||||
#[BencodeProperty]
|
||||
public private(set) int $interval,
|
||||
#[BencodeProperty('min interval')]
|
||||
public private(set) int $minInterval,
|
||||
private bool $compactPeers,
|
||||
private bool $noPeerIds
|
||||
) {}
|
||||
) {
|
||||
foreach($peers as $peerInfo)
|
||||
$peerInfo->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')]
|
||||
|
|
|
@ -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,
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -1,43 +1,30 @@
|
|||
<?php
|
||||
namespace Seria\Torrents;
|
||||
|
||||
use Index\Bencode\{BencodeProperty,BencodeSerializable,BencodeSerializableTrait};
|
||||
use Index\Bencode\{BencodeProperty,BencodeSerializable,BencodeSerializableCommon};
|
||||
use Index\Db\DbResult;
|
||||
|
||||
readonly class TorrentFileInfo implements BencodeSerializable {
|
||||
use BencodeSerializableTrait;
|
||||
class TorrentFileInfo implements BencodeSerializable {
|
||||
use BencodeSerializableCommon;
|
||||
|
||||
private string $id;
|
||||
private string $torrentId;
|
||||
private int $length;
|
||||
private string $path;
|
||||
public function __construct(
|
||||
public private(set) string $id,
|
||||
public private(set) string $torrentId,
|
||||
#[BencodeProperty] public private(set) int $length,
|
||||
public private(set) string $path,
|
||||
) {}
|
||||
|
||||
public function __construct(DbResult $result) {
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -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);
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
<?php
|
||||
namespace Seria\Torrents;
|
||||
|
||||
use Index\XArray;
|
||||
use Index\Bencode\Bencode;
|
||||
use Index\Db\DbConnection;
|
||||
use Seria\GitInfo;
|
||||
use Seria\Users\UserInfo;
|
||||
|
||||
class TorrentsContext {
|
||||
private Torrents $torrents;
|
||||
private TorrentFiles $files;
|
||||
private TorrentPeers $peers;
|
||||
private TorrentPieces $pieces;
|
||||
public private(set) Torrents $torrents;
|
||||
public private(set) TorrentFiles $files;
|
||||
public private(set) TorrentPeers $peers;
|
||||
public private(set) TorrentPieces $pieces;
|
||||
|
||||
public function __construct(DbConnection $dbConn) {
|
||||
$this->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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<h2>{{ torrent_info.name }}</h2>
|
||||
<ul>
|
||||
<li><a href="/download/{{ torrent_info.id }}"><img src="//static.flash.moe/images/silk/link.png" alt=""> Download</a></li>
|
||||
{% if globals.auth_info.isLoggedIn and globals.auth_info.userInfo.canRecalculateInfoHash %}
|
||||
{% if globals.auth_info.loggedIn and globals.auth_info.userInfo.canRecalculateInfoHash %}
|
||||
<li><a href="javascript:;" class="js-info-hash-recalc"><img src="//static.flash.moe/images/silk/calculator.png" alt=""> Recalculate Info Hash</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
@ -20,7 +20,7 @@
|
|||
<div class="info-stats-item-value"><time datetime="{{ torrent_info.createdTime|date('c') }}">{{ torrent_info.createdTime|date('Y-m-d H:i:s e') }}</time></div>
|
||||
</div>
|
||||
|
||||
{% if torrent_info.isApproved and globals.auth_info.isLoggedIn and globals.auth_info.userInfo.canApproveTorrents %}
|
||||
{% if torrent_info.approved and globals.auth_info.loggedIn and globals.auth_info.userInfo.canApproveTorrents %}
|
||||
<div class="info-stats-item info-stats-approved">
|
||||
<div class="info-stats-item-title">Approved on</div>
|
||||
<div class="info-stats-item-value"><time datetime="{{ torrent_info.approvedTime|date('c') }}">{{ torrent_info.approvedTime|date('Y-m-d H:i:s e') }}</time></div>
|
||||
|
@ -42,7 +42,7 @@
|
|||
<div class="info-stats-item-value">{{ torrent_incomplete_peers|number_format }}</div>
|
||||
</div>
|
||||
|
||||
{% if torrent_info.isPrivate %}
|
||||
{% if torrent_info.private %}
|
||||
<div class="info-stats-item info-stats-visibility">
|
||||
<div class="info-stats-item-title">Visibility</div>
|
||||
<div class="info-stats-item-value">Private</div>
|
||||
|
@ -58,7 +58,7 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not torrent_info.isApproved and globals.auth_info.isLoggedIn and globals.auth_info.userInfo.canApproveTorrents %}
|
||||
{% if not torrent_info.approved and globals.auth_info.loggedIn and globals.auth_info.userInfo.canApproveTorrents %}
|
||||
<div class="info-pending">
|
||||
<div class="info-pending-text">This torrent is pending approval.</div>
|
||||
<a class="info-pending-approve js-info-approve" href="javascript:;">APPROVE</a>
|
||||
|
@ -66,7 +66,7 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if torrent_info.hasComment %}
|
||||
{% if torrent_info.comment is not empty %}
|
||||
<div class="info-comment">
|
||||
<div class="info-comment-header">Description</div>
|
||||
<div class="info-comment-content"><pre>{{ torrent_info.comment }}</pre></div>
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
<div class="tdl-stats-downloading" title="Downloading"><div class="arrow">↓</div><div class="number">{{ torrent.incomplete_peers|number_format }}</div></div>
|
||||
</div>
|
||||
|
||||
{% if globals.auth_info.isLoggedIn %}
|
||||
{% if globals.auth_info.loggedIn %}
|
||||
<div class="tdl-actions">
|
||||
{% if not torrent_info.isApproved and globals.auth_info.userInfo.canApproveTorrents %}
|
||||
{% if not torrent_info.approved and globals.auth_info.userInfo.canApproveTorrents %}
|
||||
<a href="javascript:;" title="Approve" class="js-listing-approve" data-dlid="{{ torrent_info.id }}"><img src="//static.flash.moe/images/silk/tick.png" alt="Approve"></a>
|
||||
<a href="javascript:;" title="Deny" class="js-listing-deny" data-dlid="{{ torrent_info.id }}"><img src="//static.flash.moe/images/silk/cross.png" alt="Deny"></a>
|
||||
{% endif %}
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
<body>
|
||||
<div class="wrapper">
|
||||
<nav class="header">
|
||||
{% if globals.auth_info.isLoggedIn %}
|
||||
{% if globals.auth_info.loggedIn %}
|
||||
{% set user_info = globals.auth_info.userInfo %}
|
||||
{% set user_ratio = user_info.calculateRatio %}
|
||||
{% set user_ratio = user_info.ratio %}
|
||||
<div class="header-stats">
|
||||
<a href="/profile/{{ globals.auth_info.userName }}">{{ globals.auth_info.userName }}</a>
|
||||
· <a href="/profile/{{ globals.auth_info.userName }}/history">R: <span style="color: {{ seria_ratio_colour(user_ratio) }};">{{ user_ratio|number_format(3) }}</span></a>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
{% set title = profile_user.name %}
|
||||
|
||||
{% set profile_ratio = profile_user.calculateRatio %}
|
||||
{% set profile_ratio = profile_user.ratio %}
|
||||
|
||||
{% block content %}
|
||||
<div class="profile" style="--user-colour: {{ profile_user.colour }}" id="p{{ profile_user.id }}">
|
||||
|
|
Loading…
Add table
Reference in a new issue