implement torrent posters feature #18

This commit is contained in:
ghost 2023-10-30 04:44:44 +02:00
parent 8ae1b3f0b7
commit bd5191e894
16 changed files with 944 additions and 43 deletions

View file

@ -1463,6 +1463,7 @@ class ActivityController extends AbstractController
$this->getParameter('app.theme'),
$this->getParameter('app.sensitive'),
$this->getParameter('app.yggdrasil'),
$this->getParameter('app.posters'),
$this->getParameter('app.approved')
);

View file

@ -106,6 +106,22 @@ class TorrentController extends AbstractController
// Init page
$page = $request->get('page') ? (int) $request->get('page') : 1;
// Poster
if ($user->isPosters() && $torrent->getTorrentPosterId())
{
$poster = $request->getScheme() . '://' .
$request->getHttpHost() .
$request->getBasePath() .
$torrentService->getImageUriByTorrentPosterId(
$torrent->getTorrentPosterId()
);
}
else
{
$poster = false;
}
// Render template
return $this->render('default/torrent/info.html.twig',
[
@ -155,7 +171,7 @@ class TorrentController extends AbstractController
)
]
],
'star' =>
'star' =>
[
'exist' => (bool) $torrentService->findTorrentStar(
$torrent->getId(),
@ -165,7 +181,8 @@ class TorrentController extends AbstractController
$torrent->getId()
)
],
'contributors' => $contributors
'contributors' => $contributors,
'poster' => $poster
],
'file' =>
[
@ -284,6 +301,22 @@ class TorrentController extends AbstractController
arsort($keywords);
// Poster
if ($user->isPosters() && $torrent->getTorrentPosterId())
{
$poster = $request->getScheme() . '://' .
$request->getHttpHost() .
$request->getBasePath() .
$torrentService->getImageUriByTorrentPosterId(
$torrent->getTorrentPosterId()
);
}
else
{
$poster = false;
}
// Push torrent
$torrents[] =
[
@ -351,6 +384,7 @@ class TorrentController extends AbstractController
$torrent->getId()
)
],
'poster' => $poster
];
}
@ -447,6 +481,22 @@ class TorrentController extends AbstractController
arsort($keywords);
// Poster
if ($user->isPosters() && $torrent->getTorrentPosterId())
{
$poster = $request->getScheme() . '://' .
$request->getHttpHost() .
$request->getBasePath() .
$torrentService->getImageUriByTorrentPosterId(
$torrent->getTorrentPosterId()
);
}
else
{
$poster = false;
}
// Push torrent
$torrents[] =
[
@ -471,15 +521,6 @@ class TorrentController extends AbstractController
'peers' => (int) $torrent->getPeers(),
'leechers' => (int) $torrent->getLeechers(),
],
'user' =>
[
'id' => $torrent->getUserId(),
'identicon' => $userService->identicon(
$userService->getUser(
$torrent->getUserId()
)->getAddress()
)
],
'keywords' => $keywords,
'download' =>
[
@ -514,6 +555,7 @@ class TorrentController extends AbstractController
$torrent->getId()
)
],
'poster' => $poster
];
}
@ -1773,6 +1815,379 @@ class TorrentController extends AbstractController
);
}
// Torrent poster
#[Route(
'/{_locale}/torrent/{torrentId}/edit/poster/{torrentPosterId}',
name: 'torrent_poster_edit',
requirements:
[
'_locale' => '%app.locales%',
'torrentId' => '\d+',
'torrentPosterId' => '\d+',
],
defaults:
[
'torrentPosterId' => null,
],
methods:
[
'GET',
'POST'
]
)]
public function editPoster(
Request $request,
TranslatorInterface $translator,
UserService $userService,
TorrentService $torrentService,
ActivityService $activityService
): Response
{
// Init user
$user = $this->initUser(
$request,
$userService,
$activityService
);
if (!$user->isStatus())
{
// @TODO
throw new \Exception(
$translator->trans('Access denied')
);
}
// Init torrent
if (!$torrent = $torrentService->getTorrent($request->get('torrentId')))
{
throw $this->createNotFoundException();
}
// Init poster value
if ($request->get('torrentPosterId'))
{
if ($torrentPoster = $torrentService->getTorrentPoster($request->get('torrentPosterId')))
{
$torrentPosterCurrent =
[
'id' => $torrentPoster->getId(),
'userId' => $torrentPoster->getUserId(),
'value' => 'src' // @TODO
];
}
else
{
throw $this->createNotFoundException();
}
}
else
{
if ($torrentPoster = $torrentService->findLastTorrentPosterByTorrentId($torrent->getId()))
{
$torrentPosterCurrent =
[
'id' => $torrentPoster->getId(),
'userId' => $torrentPoster->getUserId(),
'value' => 'src' // @TODO
];
}
else
{
$torrentPosterCurrent =
[
'id' => null,
'userId' => null,
'value' => false,
];
}
}
// Init edition history
$editions = [];
foreach ($torrentService->findTorrentPosterByTorrentId($torrent->getId()) as $torrentPosterEdition)
{
$editions[] =
[
'id' => $torrentPosterEdition->getId(),
'added' => $torrentPosterEdition->getAdded(),
'approved' => $torrentPosterEdition->isApproved(),
'active' => $torrentPosterEdition->getId() == $torrentPosterCurrent['id'],
'user' =>
[
'id' => $torrentPosterEdition->getUserId(),
'identicon' => $userService->identicon(
$userService->getUser(
$torrentPosterEdition->getUserId()
)->getAddress()
),
],
'poster' =>
$request->getScheme() . '://' .
$request->getHttpHost() .
$request->getBasePath() .
$torrentService->getImageUriByTorrentPosterId(
$torrentPosterEdition->getId()
)
];
}
// Init form
$form =
[
'poster' =>
[
'error' => []
]
];
// Process request
if ($request->isMethod('post'))
{
if ($file = $request->files->get('poster'))
{
//// Validate poster file
if (filesize($file->getPathName()) > $this->getParameter('app.torrent.poster.size.max'))
{
$form['poster']['error'][] = $translator->trans('Poster file out of size limit');
}
//// Validate image format
if (!@getimagesize($file->getPathName()))
{
$form['poster']['error'][] = $translator->trans('Image file not supported');
}
}
else
{
$form['poster']['error'][] = $translator->trans('Poster file required');
}
// Request is valid
if (empty($form['poster']['error']))
{
// Save data
$torrentPoster = $torrentService->addTorrentPoster(
$file->getPathName(),
$torrent->getId(),
$user->getId(),
time(),
$user->isApproved()
);
// Add activity event
/* @TODO
$activityService->addEventTorrentPosterAdd(
$user->getId(),
$torrent->getId(),
time(),
$torrentPoster->getId()
);
*/
// Redirect to info page created
return $this->redirectToRoute(
'torrent_info',
[
'_locale' => $request->get('_locale'),
'torrentId' => $torrent->getId()
]
);
}
}
// Render form template
return $this->render(
'default/torrent/edit/poster.html.twig',
[
'torrentId' => $torrent->getId(),
'editions' => $editions,
'form' => $form,
'session' =>
[
'moderator' => $user->isModerator(),
'owner' => $torrentPosterCurrent['userId'] === $user->getId(),
]
]
);
}
#[Route(
'/{_locale}/torrent/{torrentId}/approve/poster/{torrentPosterId}',
name: 'torrent_poster_approve',
requirements:
[
'_locale' => '%app.locales%',
'torrentId' => '\d+',
'torrentPosterId' => '\d+',
],
methods:
[
'GET'
]
)]
public function approvePoster(
Request $request,
TranslatorInterface $translator,
UserService $userService,
TorrentService $torrentService,
ActivityService $activityService
): Response
{
// Init user
$user = $this->initUser(
$request,
$userService,
$activityService
);
// Init torrent
if (!$torrent = $torrentService->getTorrent($request->get('torrentId')))
{
throw $this->createNotFoundException();
}
// Init torrent poster
if (!$torrentPoster = $torrentService->getTorrentPoster($request->get('torrentPosterId')))
{
throw $this->createNotFoundException();
}
// Check permissions
if (!$user->isModerator())
{
// @TODO
throw new \Exception(
$translator->trans('Access denied')
);
}
// Add activity event
if (!$torrentPoster->isApproved())
{
/* @TODO
$activityService->addEventTorrentPosterApproveAdd(
$user->getId(),
$torrent->getId(),
time(),
$torrentPoster->getId()
);
*/
}
else
{
/* @TODO
$activityService->addEventTorrentPosterApproveDelete(
$user->getId(),
$torrent->getId(),
time(),
$torrentPoster->getId()
);
*/
}
// Update approved
$torrentService->toggleTorrentPosterApproved(
$torrentPoster->getId()
);
// Redirect
return $this->redirectToRoute(
'torrent_poster_edit',
[
'_locale' => $request->get('_locale'),
'torrentId' => $torrent->getId(),
'torrentPosterId' => $torrentPoster->getId(),
]
);
}
#[Route(
'/{_locale}/torrent/{torrentId}/delete/poster/{torrentPosterId}',
name: 'torrent_poster_delete',
requirements:
[
'_locale' => '%app.locales%',
'torrentId' => '\d+',
'torrentPosterId' => '\d+',
],
methods:
[
'GET'
]
)]
public function deletePoster(
Request $request,
TranslatorInterface $translator,
UserService $userService,
TorrentService $torrentService,
ActivityService $activityService
): Response
{
// Init user
$user = $this->initUser(
$request,
$userService,
$activityService
);
// Init torrent
if (!$torrent = $torrentService->getTorrent($request->get('torrentId')))
{
throw $this->createNotFoundException();
}
// Init torrent poster
if (!$torrentPoster = $torrentService->getTorrentPoster($request->get('torrentPosterId')))
{
throw $this->createNotFoundException();
}
// Check permissions
if (!($user->isModerator() || $user->getId() === $torrentPoster->getUserId()))
{
// @TODO
throw new \Exception(
$translator->trans('Access denied')
);
}
// Add activity event
/* @TODO
$activityService->addEventTorrentPosterDelete(
$user->getId(),
$torrent->getId(),
time(),
$torrentPoster->getId()
);
*/
// Update approved
$torrentService->deleteTorrentPoster(
$torrentPoster->getId()
);
// Redirect
return $this->redirectToRoute(
'torrent_poster_edit',
[
'_locale' => $request->get('_locale'),
'torrentId' => $torrent->getId(),
'torrentPosterId' => $torrentPoster->getId(),
]
);
}
// Torrent star
#[Route(
'/{_locale}/torrent/{torrentId}/star/toggle',
@ -2519,6 +2934,7 @@ class TorrentController extends AbstractController
$this->getParameter('app.theme'),
$this->getParameter('app.sensitive'),
$this->getParameter('app.yggdrasil'),
$this->getParameter('app.posters'),
$this->getParameter('app.approved')
);

View file

@ -117,6 +117,11 @@ class UserController extends AbstractController
$request->get('yggdrasil') === 'true'
);
// Update posters
$user->setPosters(
$request->get('posters') === 'true'
);
// Save changes to DB
$userService->save($user);
@ -137,6 +142,7 @@ class UserController extends AbstractController
'id' => $user->getId(),
'sensitive' => $user->isSensitive(),
'yggdrasil' => $user->isYggdrasil(),
'posters' => $user->isPosters(),
'locale' => $user->getLocale(),
'locales' => $user->getLocales(),
'events' => $user->getEvents(),
@ -543,6 +549,11 @@ class UserController extends AbstractController
true
);
$torrentService->setTorrentPostersApprovedByUserId(
$userTarget->getId(),
true
);
// @TODO make event for each item
}
@ -613,6 +624,7 @@ class UserController extends AbstractController
$this->getParameter('app.theme'),
$this->getParameter('app.sensitive'),
$this->getParameter('app.yggdrasil'),
$this->getParameter('app.posters'),
$this->getParameter('app.approved')
);

View file

@ -51,6 +51,9 @@ class Torrent
#[ORM\Column(nullable: true)]
private ?int $leechers = null;
#[ORM\Column(nullable: true)]
private ?int $torrentPosterId = null;
public function getId(): ?int
{
return $this->id;
@ -206,4 +209,16 @@ class Torrent
return $this;
}
public function getTorrentPosterId(): ?int
{
return $this->torrentPosterId;
}
public function setTorrentPosterId(int $torrentPosterId): static
{
$this->torrentPosterId = $torrentPosterId;
return $this;
}
}

View file

@ -25,9 +25,6 @@ class TorrentPoster
#[ORM\Column]
private ?bool $approved = null;
#[ORM\Column(length: 255)]
private ?string $color = null;
#[ORM\Column(length: 32)]
private ?string $md5file = null;
@ -91,18 +88,6 @@ class TorrentPoster
return $this;
}
public function getColor(): ?string
{
return $this->color;
}
public function setColor(string $color): static
{
$this->color = $color;
return $this;
}
public function getMd5file(): ?string
{
return $this->md5file;

View file

@ -47,6 +47,9 @@ class User
#[ORM\Column]
private ?bool $yggdrasil = null;
#[ORM\Column]
private ?bool $posters = null;
public function getId(): ?int
{
return $this->id;
@ -190,4 +193,16 @@ class User
return $this;
}
public function isPosters(): ?bool
{
return $this->posters;
}
public function setPosters(bool $posters): static
{
$this->posters = $posters;
return $this;
}
}

View file

@ -5,6 +5,7 @@ namespace App\Service;
use App\Entity\Torrent;
use App\Entity\TorrentLocales;
use App\Entity\TorrentSensitive;
use App\Entity\TorrentPoster;
use App\Entity\TorrentStar;
use App\Entity\TorrentDownloadFile;
use App\Entity\TorrentDownloadMagnet;
@ -12,6 +13,7 @@ use App\Entity\TorrentDownloadMagnet;
use App\Repository\TorrentRepository;
use App\Repository\TorrentLocalesRepository;
use App\Repository\TorrentSensitiveRepository;
use App\Repository\TorrentPosterRepository;
use App\Repository\TorrentStarRepository;
use App\Repository\TorrentDownloadFileRepository;
use App\Repository\TorrentDownloadMagnetRepository;
@ -214,10 +216,93 @@ class TorrentService
);
}
public function getImageUriByTorrentPosterId(
int $torrentPosterId,
int $quality = 100,
int $width = 748,
int $height = 0,
float $opacity = 1,
bool $grayscale = false,
string $format = 'webp'
): string
{
$uri = sprintf(
'/posters/%s.%s',
implode('/', str_split($torrentPosterId)),
$format
);
$filename = sprintf(
'%s/public/posters/%s.%s',
$this->kernelInterface->getProjectDir(),
implode('/', str_split($torrentPosterId)),
$format
);
if (file_exists($filename))
{
return $uri;
}
$path = explode('/', $filename);
array_pop($path);
@mkdir(implode('/', $path), 0755, true);
$image = new \Imagick();
$image->readImage(
$this->getStorageFilepathByTorrentPosterId(
$torrentPosterId
)
);
$image->setImageFormat($format);
$image->setImageCompressionQuality($quality);
if ($width || $height)
{
$image->adaptiveResizeImage(
$width,
$height
);
}
if ($grayscale)
{
$image->setImageType(
\Imagick::IMGTYPE_GRAYSCALE
);
}
if ($opacity)
{
$image->setImageOpacity(
$opacity
);
}
$image->writeImage(
$filename
);
return $uri;
}
public function getStorageFilepathByTorrentPosterId(int $torrentPosterId): string
{
return sprintf(
'%s/var/posters/%s',
$this->kernelInterface->getProjectDir(),
implode('/', str_split($torrentPosterId))
);
}
public function getStorageFilepathByTorrentId(int $torrentId): string
{
return sprintf(
'%s/var/torrents/%s.torrent',
'%s/var/torrents/%s.torrent', // @TODO remove extension as not required in background storage
$this->kernelInterface->getProjectDir(),
implode('/', str_split($torrentId))
);
@ -932,6 +1017,209 @@ class TorrentService
}
}
// Torrent poster
public function getTorrentPoster(
int $torrentPosterId
): ?TorrentPoster
{
return $this->entityManagerInterface
->getRepository(TorrentPoster::class)
->find(
$torrentPosterId
);
}
public function findTorrentPosterByMd5File(
string $md5file
): ?Torrent
{
return $this->entityManagerInterface
->getRepository(TorrentPoster::class)
->findOneBy(
[
'md5file' => $md5file
]
);
}
public function findLastTorrentPosterByTorrentId(
int $torrentId
): ?TorrentPoster
{
return $this->entityManagerInterface
->getRepository(TorrentPoster::class)
->findOneBy(
[
'torrentId' => $torrentId
],
[
'id' => 'DESC'
]
);
}
public function findTorrentPosterByTorrentId(
int $torrentId
): array
{
return $this->entityManagerInterface
->getRepository(TorrentPoster::class)
->findBy(
[
'torrentId' => $torrentId
],
[
'id' => 'DESC'
]
);
}
public function toggleTorrentPosterApproved(
int $torrentPosterId
): ?TorrentPoster
{
$torrentPoster = $this->entityManagerInterface
->getRepository(TorrentPoster::class)
->find($torrentPosterId);
$torrentPoster->setApproved(
!$torrentPoster->isApproved() // toggle current value
);
$this->entityManagerInterface->persist($torrentPoster);
$this->entityManagerInterface->flush();
$this->updateTorrentPoster(
$torrentPoster->getTorrentId()
);
return $torrentSensitive;
}
public function deleteTorrentPoster(
int $torrentPosterId
): ?TorrentPoster
{
// Remove torrent file from permanent storage
$filesystem = new Filesystem();
$filesystem->remove(
$this->getStorageFilepathByTorrentPosterId(
$torrentPosterId
)
);
// Remove from DB
$torrentPoster = $this->getTorrentPoster(
$torrentPosterId
);
$this->entityManagerInterface->remove($torrentPoster);
$this->entityManagerInterface->flush();
// Update torrent
$this->updateTorrentPoster(
$torrentPoster->getTorrentId()
);
return $torrentSensitive;
}
public function addTorrentPoster(
string $filename,
int $torrentId,
int $userId,
int $added,
bool $approved
): ?TorrentPoster
{
// Add new DB record
$torrentPoster = new TorrentPoster();
$torrentPoster->setTorrentId($torrentId);
$torrentPoster->setUserId($userId);
$torrentPoster->setAdded($added);
$torrentPoster->setApproved($approved);
$torrentPoster->setMd5file(
md5_file($filename)
);
$this->entityManagerInterface->persist($torrentPoster);
$this->entityManagerInterface->flush();
// Save file in permanent storage
$filesystem = new Filesystem();
$filesystem->copy(
$filename,
$this->getStorageFilepathByTorrentPosterId(
$torrentPoster->getId()
)
);
// Update torrent info
$this->updateTorrentPoster(
$torrentId
);
return $torrentPoster;
}
public function setTorrentPostersApprovedByUserId(
int $userId,
bool $value
): void
{
foreach ($this->entityManagerInterface
->getRepository(TorrentPoster::class)
->findBy(
[
'userId' => $userId
]) as $torrentPoster)
{
$torrentPoster->setApproved(
$value
);
$this->entityManagerInterface->persist($torrentPoster);
$this->entityManagerInterface->flush();
$this->updateTorrentPoster(
$torrentPoster->getTorrentId(),
);
}
}
public function updateTorrentPoster(
int $torrentId,
): void
{
if ($torrent = $this->getTorrent($torrentId))
{
if ($torrentPoster = $this->entityManagerInterface
->getRepository(TorrentPoster::class)
->findOneBy(
[
'torrentId' => $torrentId,
'approved' => true,
],
[
'id' => 'DESC'
]
))
{
$torrent->setTorrentPosterId(
$torrentPoster->getId()
);
$this->entityManagerInterface->persist($torrent);
$this->entityManagerInterface->flush();
}
}
}
// Torrent star
public function findTorrentStar(
int $torrentId,

View file

@ -32,6 +32,7 @@ class UserService
string $theme,
bool $sensitive = true,
bool $yggdrasil = true,
bool $posters = true,
bool $approved = false,
bool $moderator = false,
bool $status = true
@ -84,6 +85,10 @@ class UserService
$yggdrasil
);
$user->setPosters(
$posters
);
$this->entityManagerInterface->persist($user);
$this->entityManagerInterface->flush();