206 lines
6.6 KiB
PHP
206 lines
6.6 KiB
PHP
<?php
|
|
namespace Seria\Torrents;
|
|
|
|
use Exception;
|
|
use InvalidArgumentException;
|
|
use RuntimeException;
|
|
use Index\Data\DbTools;
|
|
use Index\Data\IDbTransactions;
|
|
use Index\Serialisation\Bencode;
|
|
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();
|
|
}
|
|
|
|
public function setUser(?UserInfo $userInfo): self {
|
|
return $this->setUserId($userInfo?->getId());
|
|
}
|
|
|
|
public function setUserId(?string $userId): self {
|
|
$this->userId = $userId;
|
|
return $this;
|
|
}
|
|
|
|
public function setName(string $name): self {
|
|
if(empty($name))
|
|
throw new InvalidArgumentException('$name may not be empty.');
|
|
$this->name = $name;
|
|
return $this;
|
|
}
|
|
|
|
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 function setPieceLength(int $pieceLength): self {
|
|
if($pieceLength < 1)
|
|
throw new InvalidArgumentException('$pieceLength is not a valid piece length.');
|
|
$this->pieceLength = $pieceLength;
|
|
return $this;
|
|
}
|
|
|
|
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 {
|
|
if(is_array($path))
|
|
$path = implode('/', $path);
|
|
|
|
$path = trim($path, '/');
|
|
if(array_key_exists($path, $this->files))
|
|
throw new RuntimeException('Duplicate file.');
|
|
|
|
$this->files[$path] = [
|
|
'length' => $length,
|
|
'path' => explode('/', $path),
|
|
];
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function addPiece(string $hash): self {
|
|
if(strlen($hash) !== 20)
|
|
throw new InvalidArgumentException('$hash is not a valid piece hash.');
|
|
|
|
$this->pieces[] = $hash;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function calculateInfoHash(): string {
|
|
$info = [
|
|
'files' => array_values($this->files),
|
|
'name' => $this->name,
|
|
'piece length' => $this->pieceLength,
|
|
'pieces' => implode($this->pieces),
|
|
];
|
|
|
|
if(!empty($this->isPrivate))
|
|
$info['private'] = 1;
|
|
|
|
return hash('sha1', Bencode::encode($info), true);
|
|
}
|
|
|
|
public function create(IDbTransactions $dbConn, TorrentsContext $torrentsCtx): string {
|
|
$torrents = $torrentsCtx->getTorrents();
|
|
$pieces = $torrentsCtx->getPieces();
|
|
$files = $torrentsCtx->getFiles();
|
|
|
|
$dbConn->beginTransaction();
|
|
|
|
try {
|
|
$infoHash = $this->calculateInfoHash();
|
|
$torrentId = $torrents->createTorrent(
|
|
$this->userId, $infoHash, $this->name, $this->created,
|
|
$this->pieceLength, $this->isPrivate, $this->comment
|
|
);
|
|
|
|
foreach($this->files as $file)
|
|
$files->createFile($torrentId, $file['length'], $file['path']);
|
|
|
|
foreach($this->pieces as $piece)
|
|
$pieces->createPiece($torrentId, $piece);
|
|
|
|
$dbConn->commit();
|
|
} catch(Exception $ex) {
|
|
$dbConn->rollBack();
|
|
throw $ex;
|
|
}
|
|
|
|
return $torrentId;
|
|
}
|
|
|
|
public static function import(
|
|
TorrentInfo $torrent,
|
|
array $pieces,
|
|
array $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());
|
|
|
|
foreach($pieces as $piece)
|
|
$builder->addPiece($piece->getHash());
|
|
|
|
foreach($files as $file)
|
|
$builder->addFile($file->getPath(), $file->getLength());
|
|
|
|
return $builder;
|
|
}
|
|
|
|
public static function decode(mixed $source): self {
|
|
if(is_string($source) || is_resource($source))
|
|
$source = Bencode::decode($source);
|
|
|
|
if(!isset($source['info']) || !is_array($source['info']))
|
|
throw new InvalidArgumentException('info key missing.');
|
|
if(!isset($source['info']['name']) || !is_string($source['info']['name']))
|
|
throw new InvalidArgumentException('info.name key missing.');
|
|
if(!isset($source['info']['files']) || !is_array($source['info']['files']))
|
|
throw new InvalidArgumentException('info.files key missing.');
|
|
if(!isset($source['info']['pieces']) || !is_string($source['info']['pieces']))
|
|
throw new InvalidArgumentException('info.pieces key missing.');
|
|
if(!isset($source['info']['piece length']) || !is_int($source['info']['piece length']))
|
|
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']));
|
|
|
|
if(isset($source['creation date'])
|
|
&& is_int($source['creation date']))
|
|
$builder->setCreatedTime($source['creation date']);
|
|
|
|
if(!empty($source['comment']))
|
|
$builder->setComment($source['comment']);
|
|
|
|
foreach($source['info']['files'] as $file) {
|
|
if(empty($file)
|
|
|| !is_array($file)
|
|
|| !isset($file['length'])
|
|
|| !is_int($file['length'])
|
|
|| !isset($file['path'])
|
|
|| !is_array($file['path']))
|
|
throw new InvalidArgumentException('Invalid info.files entry.');
|
|
|
|
foreach($file['path'] as $pathPart)
|
|
if(!is_string($pathPart))
|
|
throw new InvalidArgumentException('Invalid info.files entry path.');
|
|
|
|
$builder->addFile($file['path'], $file['length']);
|
|
}
|
|
|
|
$pieces = str_split($source['info']['pieces'], 20);
|
|
foreach($pieces as $piece)
|
|
$builder->addPiece($piece);
|
|
|
|
return $builder;
|
|
}
|
|
}
|