revamp torrent log uploading

This commit is contained in:
Spine
2023-03-04 11:41:56 +00:00
parent e34f495b9a
commit c293a6a9e3
24 changed files with 340 additions and 298 deletions

View File

@@ -2,94 +2,44 @@
namespace Gazelle\Json; namespace Gazelle\Json;
use Gazelle\File\RipLog;
use Gazelle\File\RipLogHTML;
use Gazelle\Logfile;
use Gazelle\LogfileSummary;
use OrpheusNET\Logchecker\Logchecker; use OrpheusNET\Logchecker\Logchecker;
class AddLog extends \Gazelle\Json { class AddLog extends \Gazelle\Json {
public function __construct( public function __construct(
protected \Gazelle\Torrent $torrent, protected \Gazelle\Torrent $torrent,
protected \Gazelle\User $user, protected \Gazelle\User $user,
protected array $files, protected \Gazelle\Manager\TorrentLog $torrentLogManager,
protected \Gazelle\LogfileSummary $logfileSummary,
) {} ) {}
public function payload(): ?array { public function payload(): ?array {
$logfiles = [];
if ($this->user->id() !== $this->torrent->uploaderId() && !$this->user->permitted('admin_add_log')) { if ($this->user->id() !== $this->torrent->uploaderId() && !$this->user->permitted('admin_add_log')) {
$this->failure('Not the torrent owner or moderator'); $this->failure('Not the torrent owner or moderator');
return null; return null;
} }
$logfileSummary = new LogfileSummary;
$ripFiler = new RipLog;
$htmlFiler = new RipLogHTML;
$torrentId = $this->torrent->id();
$logSummaries = []; $logSummaries = [];
for ($i = 0, $total = count($this->files['name']); $i < $total; $i++) { $checkerVersion = Logchecker::getLogcheckerVersion();
if (!$this->files['size'][$i]) { foreach($this->logfileSummary->all() as $logfile) {
continue; $this->torrentLogManager->create($this->torrent, $logfile, $checkerVersion);
}
$logfile = new Logfile(
$this->files['tmp_name'][$i],
$this->files['name'][$i]
);
$logfiles[] = $logfile;
$logfileSummary->add($logfile);
self::$db->prepared_query('
INSERT INTO torrents_logs
(TorrentID, Score, `Checksum`, FileName, Ripper, RipperVersion, `Language`, ChecksumState, LogcheckerVersion, Details)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
', $torrentId, $logfile->score(), $logfile->checksumStatus(), $logfile->filename(), $logfile->ripper(),
$logfile->ripperVersion(), $logfile->language(), $logfile->checksumState(),
Logchecker::getLogcheckerVersion(), $logfile->detailsAsString()
);
$logId = self::$db->inserted_id();
$ripFiler->put($logfile->filepath(), [$torrentId, $logId]);
$htmlFiler->put($logfile->text(), [$torrentId, $logId]);
$logSummaries[] = [ $logSummaries[] = [
'score' => $logfile->score(), 'score' => $logfile->score(),
'checksum' => $logfile->checksumState(), 'checksum' => $logfile->checksumState(),
'ripper' => $logfile->ripper(), 'ripper' => $logfile->ripper(),
'ripperVersion' => $logfile->ripperVersion(), 'ripperVersion' => $logfile->ripperVersion(),
'language' => $logfile->language(), 'language' => $logfile->language(),
'details' => $logfile->detailsAsString() 'details' => $logfile->detailsAsString(),
]; ];
} }
$this->torrent->updateLogScore($logfileSummary); $this->torrent->modifyLogscore();
$this->torrent->flush();
[$score, $checksum] = self::$db->row("
SELECT min(CASE WHEN Adjusted = '1' THEN AdjustedScore ELSE Score END) AS Score,
min(CASE WHEN Adjusted = '1' THEN AdjustedChecksum ELSE Checksum END) AS Checksum
FROM torrents_logs
WHERE TorrentID = ?
GROUP BY TorrentID
", $torrentId
);
self::$db->prepared_query(
'UPDATE torrents SET LogScore = ?, LogChecksum = ?, HasLogDB = ? WHERE ID = ?',
$score, $checksum, '1', $torrentId
);
$groupId = $this->torrent->groupId();
self::$cache->delete_multi([
"torrent_group_{$groupId}",
"torrents_details_{$groupId}",
sprintf(\Gazelle\TGroup::CACHE_KEY, $groupId),
sprintf(\Gazelle\TGroup::CACHE_TLIST_KEY, $groupId),
]);
return [ return [
'torrentId' => $torrentId, 'torrentId' => $this->torrent->id(),
'score' => $score, 'score' => $this->torrent->logScore(),
'checksum' => $checksum, 'checksum' => $this->torrent->logChecksum(),
'logcheckerVersion' => Logchecker::getLogcheckerVersion(), 'logcheckerVersion' => $checkerVersion,
'logSummaries' => $logSummaries 'logSummaries' => $logSummaries,
]; ];
} }
} }

View File

@@ -22,13 +22,13 @@ class Logfile {
$checker = new Logchecker(); $checker = new Logchecker();
$checker->newFile($this->filepath); $checker->newFile($this->filepath);
$checker->parse(); $checker->parse();
$this->score = max(0, $checker->getScore()); $this->score = max(0, $checker->getScore());
$this->details = $checker->getDetails(); $this->details = $checker->getDetails();
$this->checksumState = $checker->getChecksumState(); $this->checksumState = $checker->getChecksumState();
$this->text = $checker->getLog(); $this->text = $checker->getLog();
$this->ripper = $checker->getRipper() ?? ''; $this->ripper = $checker->getRipper() ?? '';
$this->ripperVersion = $checker->getRipperVersion() ?? ''; $this->ripperVersion = $checker->getRipperVersion() ?? '';
$this->language = $checker->getLanguage(); $this->language = $checker->getLanguage();
} }
public function checksum() { return $this->checksumState === Checksum::CHECKSUM_OK; } public function checksum() { return $this->checksumState === Checksum::CHECKSUM_OK; }

View File

@@ -3,42 +3,39 @@
namespace Gazelle; namespace Gazelle;
class LogfileSummary { class LogfileSummary {
/** @var Logfile[] */ protected array $list;
protected $list; protected bool $allChecksum = true;
protected $allChecksum; protected int $lowestScore = 100;
protected $lowestScore;
public function __construct() { public function __construct(array $fileList = []) {
$this->list = []; $this->list = [];
for ($n = 0, $end = count($fileList['error']); $n < $end; ++$n) {
if ($fileList['error'][$n] == UPLOAD_ERR_OK) {
$log = new Logfile($fileList['tmp_name'][$n], $fileList['name'][$n]);
$this->allChecksum = $this->allChecksum && $log->checksum();
$this->lowestScore = min($this->lowestScore, $log->score());
$this->list[] = $log;
}
}
} }
public function add(Logfile $log) { public function checksum(): bool {
$this->list[] = $log;
$this->allChecksum = is_null($this->allChecksum)
? $log->checksum()
: $this->allChecksum && $log->checksum();
$this->lowestScore = is_null($this->lowestScore)
? $log->score()
: min($this->lowestScore, $log->score());
}
public function checksum() {
return $this->allChecksum; return $this->allChecksum;
} }
public function checksumStatus() { public function checksumStatus(): string {
return $this->allChecksum ? '1' : '0'; return $this->allChecksum ? '1' : '0';
} }
public function overallScore() { public function overallScore(): int {
return is_null($this->lowestScore) ? 0 : $this->lowestScore; return is_null($this->lowestScore) ? 0 : $this->lowestScore;
} }
public function all() { public function all(): array {
return $this->list; return $this->list;
} }
public function count() { public function total(): int {
return count($this->list); return count($this->list);
} }
} }

View File

@@ -99,27 +99,27 @@ class Torrent extends \Gazelle\BaseManager {
} }
public function create( public function create(
int $tgroupId, int $tgroupId,
int $userId, int $userId,
string $description, string $description,
string $media, string $media,
?string $format, ?string $format,
?string $encoding, ?string $encoding,
string $infohash, string $infohash,
string $filePath, string $filePath,
array $fileList, array $fileList,
int $size, int $size,
bool $isScene, bool $isScene,
bool $isRemaster, bool $isRemaster,
?int $remasterYear, ?int $remasterYear,
string $remasterTitle, string $remasterTitle,
string $remasterRecordLabel, string $remasterRecordLabel,
string $remasterCatalogueNumber, string $remasterCatalogueNumber,
int $logScore = 0, int $logScore = 0,
bool $hasChecksum = false, bool $hasChecksum = false,
bool $hasCue = false, bool $hasCue = false,
bool $hasLog = false, bool $hasLog = false,
bool $hasLogInDB = false, bool $hasLogInDB = false,
): \Gazelle\Torrent { ): \Gazelle\Torrent {
self::$db->prepared_query(" self::$db->prepared_query("
INSERT INTO torrents ( INSERT INTO torrents (

View File

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

View File

@@ -220,9 +220,9 @@ class Torrent extends TorrentAbstract {
if (!$count) { if (!$count) {
self::$db->prepared_query(" self::$db->prepared_query("
UPDATE torrents SET UPDATE torrents SET
HasLogDB = '0', HasLogDB = '0',
LogChecksum = '1', LogChecksum = '0',
LogScore = 0 LogScore = 0
WHERE ID = ? WHERE ID = ?
", $this->id ", $this->id
); );
@@ -231,21 +231,23 @@ class Torrent extends TorrentAbstract {
UPDATE torrents AS t UPDATE torrents AS t
LEFT JOIN ( LEFT JOIN (
SELECT TorrentID, SELECT TorrentID,
min(CASE WHEN Adjusted = '1' OR AdjustedScore != Score THEN AdjustedScore ELSE Score END) AS Score, min(CASE WHEN Adjusted = '1' AND AdjustedScore != Score THEN AdjustedScore ELSE Score END) AS Score,
min(CASE WHEN Adjusted = '1' OR AdjustedChecksum != Checksum THEN AdjustedChecksum ELSE Checksum END) AS Checksum min(CASE WHEN Adjusted = '1' AND AdjustedChecksum != Checksum THEN AdjustedChecksum ELSE Checksum END) AS Checksum
FROM torrents_logs FROM torrents_logs
WHERE TorrentID = ? WHERE TorrentID = ?
GROUP BY TorrentID GROUP BY TorrentID
) AS tl ON (t.ID = tl.TorrentID) ) AS tl ON (t.ID = tl.TorrentID)
SET SET
t.HasLogDB = '1',
t.LogScore = tl.Score, t.LogScore = tl.Score,
t.LogChecksum = tl.Checksum t.LogChecksum = tl.Checksum
WHERE t.ID = ? WHERE t.ID = ?
", $this->id, $this->id ", $this->id, $this->id
); );
} }
$affected = self::$db->affected_rows();
$this->flush(); $this->flush();
return self::$db->affected_rows(); return $affected;
} }
public function rescoreLog(int $logId, \Gazelle\Logfile $logfile, string $version): int { public function rescoreLog(int $logId, \Gazelle\Logfile $logfile, string $version): int {
@@ -265,21 +267,6 @@ class Torrent extends TorrentAbstract {
return 0; return 0;
} }
public function updateLogScore(LogfileSummary $summary): int {
self::$db->prepared_query("
UPDATE torrents SET
HasLogDB = '1',
LogScore = ?,
LogChecksum = ?
WHERE ID = ?
", $summary->overallScore(), $summary->checksumStatus(),
$this->id
);
$this->flush();
self::$cache->delete_value(sprintf(TGroup::CACHE_TLIST_KEY, $this->groupId()));
return self::$db->affected_rows();
}
/** /**
* Remove all logfiles attached to this upload * Remove all logfiles attached to this upload
* *

View File

@@ -10,7 +10,10 @@ abstract class TorrentAbstract extends BaseObject {
protected User $viewer; protected User $viewer;
public function flush(): TorrentAbstract { public function flush(): TorrentAbstract {
self::$cache->delete_value(sprintf(self::CACHE_KEY, $this->id)); self::$cache->delete_multi([
sprintf(self::CACHE_KEY, $this->id),
"torrent_download_{$this->id}",
]);
$this->group()->flush(); $this->group()->flush();
return $this; return $this;
} }

View File

@@ -11,6 +11,9 @@ module.exports = defineConfig({
supportFile: 'tests/cypress/support/e2e.{js,jsx,ts,tsx}', supportFile: 'tests/cypress/support/e2e.{js,jsx,ts,tsx}',
specPattern: 'tests/cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', specPattern: 'tests/cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
video: false, video: false,
screenshotOnRunFailure: false screenshotOnRunFailure: false,
setupNodeEvents(on, config) {
require('cypress-terminal-report/src/installLogsPrinter')(on);
}
}, },
}); });

View File

@@ -23,6 +23,7 @@
"browser-sync": "^2.27.5", "browser-sync": "^2.27.5",
"browser-sync-webpack-plugin": "^2.2.2", "browser-sync-webpack-plugin": "^2.2.2",
"cypress": "^12.0.2", "cypress": "^12.0.2",
"cypress-terminal-report": "^5.0.2",
"husky": "^8.0.1", "husky": "^8.0.1",
"lint-staged": "^10.5.0", "lint-staged": "^10.5.0",
"stylelint": "^13.3.2", "stylelint": "^13.3.2",

View File

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

View File

@@ -1,12 +1,15 @@
<?php <?php
if (isset($_FILES['log']) && is_uploaded_file($_FILES['log']['tmp_name'])) { if (isset($_FILES['log']) && is_uploaded_file($_FILES['log']['tmp_name'])) {
$file = $_FILES['log']; $file = $_FILES['log'];
$isPaste = false; $isPaste = false;
} elseif (!empty($_POST["pastelog"])) { } elseif (!empty($_POST["pastelog"])) {
$fileTmp = tempnam('/tmp', 'log_'); $fileTmp = tempnam('/tmp', 'log_');
file_put_contents($fileTmp, $_POST["pastelog"]); file_put_contents($fileTmp, $_POST["pastelog"]);
$file = ['tmp_name' => $fileTmp, 'name' => $fileTmp]; $file = [
'tmp_name' => $fileTmp,
'name' => $fileTmp
];
$isPaste = true; $isPaste = true;
} else { } else {
json_error('no log file provided'); json_error('no log file provided');
@@ -17,13 +20,11 @@ if (isset($fileTmp)) {
unlink($fileTmp); unlink($fileTmp);
} }
$response = [ json_print('success', [
'ripper' => $logfile->ripper(), 'ripper' => $logfile->ripper(),
'ripperVersion' => $logfile->ripperVersion(), 'ripperVersion' => $logfile->ripperVersion(),
'language' => $logfile->language(), 'language' => $logfile->language(),
'score' => $logfile->score(), 'score' => $logfile->score(),
'checksum' => $logfile->checksumState(), 'checksum' => $logfile->checksumState(),
'issues' => $logfile->details(), 'issues' => $logfile->details(),
]; ]);
json_print('success', $response);

View File

@@ -1,12 +1,15 @@
<?php <?php
if (isset($_FILES['log']) && is_uploaded_file($_FILES['log']['tmp_name'])) { if (isset($_FILES['log']) && is_uploaded_file($_FILES['log']['tmp_name'])) {
$file = $_FILES['log']; $file = $_FILES['log'];
$isPaste = false; $isPaste = false;
} elseif (!empty($_POST["pastelog"])) { } elseif (!empty($_POST["pastelog"])) {
$fileTmp = tempnam('/tmp', 'log_'); $fileTmp = tempnam('/tmp', 'log_');
file_put_contents($fileTmp, $_POST["pastelog"]); file_put_contents($fileTmp, $_POST["pastelog"]);
$file = ['tmp_name' => $fileTmp, 'name' => $fileTmp]; $file = [
'tmp_name' => $fileTmp,
'name' => $fileTmp
];
$isPaste = true; $isPaste = true;
} else { } else {
error('No log file uploaded or file is empty.'); error('No log file uploaded or file is empty.');

View File

@@ -15,37 +15,26 @@ if ($torrent->uploaderId() != $Viewer->id() && !$Viewer->permitted('admin_add_lo
error('Not your upload.'); error('Not your upload.');
} }
// Some browsers will report an empty file when you submit, prune those out
$_FILES['logfiles']['name'] = array_filter($_FILES['logfiles']['name'], fn($Name) => !empty($Name));
if (count($_FILES['logfiles']['name']) == 0) {
error("No logfiles uploaded.\n");
}
$action = in_array($_POST['from_action'], ['upload', 'update']) ? $_POST['from_action'] : 'upload'; $action = in_array($_POST['from_action'], ['upload', 'update']) ? $_POST['from_action'] : 'upload';
$logfileSummary = new Gazelle\LogfileSummary($_FILES['logfiles']);
$ripFiler = new Gazelle\File\RipLog; if (!$logfileSummary->total()) {
$ripFiler->remove([$torrent->id(), null]); error("No logfiles uploaded.");
} else {
$ripFiler = new Gazelle\File\RipLog;
$htmlFiler = new Gazelle\File\RipLogHTML;
$htmlFiler = new Gazelle\File\RipLogHTML; $torrent->removeLogDb();
$htmlFiler->remove([$torrent->id(), null]); $ripFiler->remove([$torrent->id(), null]);
$htmlFiler->remove([$torrent->id(), null]);
$torrentLogManager = new Gazelle\Manager\TorrentLog($ripFiler, $htmlFiler);
$torrent->removeLogDb(); $checkerVersion = Logchecker::getLogcheckerVersion();
foreach($logfileSummary->all() as $logfile) {
$logfileSummary = new Gazelle\LogfileSummary; $torrentLogManager->create($torrent, $logfile, $checkerVersion);
foreach ($_FILES['logfiles']['name'] as $Pos => $File) {
if (!$_FILES['logfiles']['size'][$Pos]) {
break;
} }
$logfile = new Gazelle\Logfile( $torrent->modifyLogscore();
$_FILES['logfiles']['tmp_name'][$Pos],
$_FILES['logfiles']['name'][$Pos]
);
$logfileSummary->add($logfile);
$logId = $torrent->addLogDb($logfile, Logchecker::getLogcheckerVersion());
$ripFiler->put($logfile->filepath(), [$torrent->id(), $logId]);
$htmlFiler->put($logfile->text(), [$torrent->id(), $logId]);
} }
$torrent->updateLogScore($logfileSummary);
echo $Twig->render('logchecker/result.twig', [ echo $Twig->render('logchecker/result.twig', [
'summary' => $logfileSummary->all(), 'summary' => $logfileSummary->all(),

View File

@@ -1,5 +1,6 @@
<?php <?php
authorize();
if (!$Viewer->permitted('users_mod')) { if (!$Viewer->permitted('users_mod')) {
error(403); error(403);
} }

View File

@@ -8,7 +8,7 @@ $torrent = (new Gazelle\Manager\Torrent)->findById((int)($_GET['torrentid'] ?? 0
if (is_null($torrent)) { if (is_null($torrent)) {
error(404); error(404);
} }
$tlog = (new Gazelle\Manager\TorrentLog)->findById($torrent, (int)($_GET['logid'] ?? 0)); $tlog = (new Gazelle\Manager\TorrentLog(new Gazelle\File\RipLog, new Gazelle\File\RipLogHTML))->findById($torrent, (int)($_GET['logid'] ?? 0));
if (is_null($tlog)) { if (is_null($tlog)) {
error(404); error(404);
} }

View File

@@ -1,5 +1,6 @@
<?php <?php
authorize();
if (!$Viewer->permitted('torrents_delete')) { if (!$Viewer->permitted('torrents_delete')) {
error(403); error(403);
} }

View File

@@ -47,16 +47,16 @@ if (isset($_POST['album_desc'])) {
$Properties['GroupDescription'] = trim($_POST['album_desc']); $Properties['GroupDescription'] = trim($_POST['album_desc']);
} }
if ($Properties['Remastered']) { if ($Properties['Remastered']) {
$Properties['UnknownRelease'] = isset($_POST['unknown']); $Properties['UnknownRelease'] = isset($_POST['unknown']);
$Properties['RemasterYear'] = isset($_POST['remaster_year']) ? (int)$_POST['remaster_year'] : null; $Properties['RemasterYear'] = isset($_POST['remaster_year']) ? (int)$_POST['remaster_year'] : null;
$Properties['RemasterTitle'] = trim($_POST['remaster_title'] ?? ''); $Properties['RemasterTitle'] = trim($_POST['remaster_title'] ?? '');
$Properties['RemasterRecordLabel'] = trim($_POST['remaster_record_label'] ?? ''); $Properties['RemasterRecordLabel'] = trim($_POST['remaster_record_label'] ?? '');
$Properties['RemasterCatalogueNumber'] = trim($_POST['remaster_catalogue_number'] ?? ''); $Properties['RemasterCatalogueNumber'] = trim($_POST['remaster_catalogue_number'] ?? '');
} else { } else {
$Properties['UnknownRelease'] = false; $Properties['UnknownRelease'] = false;
$Properties['RemasterYear'] = null; $Properties['RemasterYear'] = null;
$Properties['RemasterTitle'] = ''; $Properties['RemasterTitle'] = '';
$Properties['RemasterRecordLabel'] = ''; $Properties['RemasterRecordLabel'] = '';
$Properties['RemasterCatalogueNumber'] = ''; $Properties['RemasterCatalogueNumber'] = '';
} }
@@ -199,42 +199,20 @@ foreach ($current as $key => $value) {
} }
} }
$logfileSummary = new Gazelle\LogfileSummary($_FILES['logfiles']);
//******************************************************************************//
//--------------- Start database stuff -----------------------------------------//
$db->begin_transaction(); // It's all or nothing $db->begin_transaction(); // It's all or nothing
// Some browsers will report an empty file when you submit, prune those out if ($logfileSummary->total()) {
if (isset($_FILES['logfiles'])) { $torrentLogManager = new Gazelle\Manager\TorrentLog(new Gazelle\File\RipLog, new Gazelle\File\RipLogHTML);
$_FILES['logfiles']['name'] = array_filter($_FILES['logfiles']['name'], fn($name) => !empty($name)); $checkerVersion = Logchecker::getLogcheckerVersion();
foreach($logfileSummary->all() as $logfile) {
$logfileSummary = new Gazelle\LogfileSummary; $torrentLogManager->create($torrent, $logfile, $checkerVersion);
$logfiles = [];
if (count($_FILES['logfiles']['name']) > 0) {
ini_set('upload_max_filesize', 1_000_000);
$ripFiler = new Gazelle\File\RipLog;
$htmlFiler = new Gazelle\File\RipLogHTML;
foreach ($_FILES['logfiles']['name'] as $Pos => $File) {
if (!$_FILES['logfiles']['size'][$Pos]) {
continue;
}
$logfile = new Gazelle\Logfile(
$_FILES['logfiles']['tmp_name'][$Pos],
$_FILES['logfiles']['name'][$Pos]
);
$logfileSummary->add($logfile);
$db->prepared_query('
INSERT INTO torrents_logs
(TorrentID, Score, `Checksum`, FileName, Ripper, RipperVersion, `Language`, ChecksumState, LogcheckerVersion, Details)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
', $TorrentID, $logfile->score(), $logfile->checksumStatus(), $logfile->filename(), $logfile->ripper(),
$logfile->ripperVersion(), $logfile->language(), $logfile->checksumState(),
Logchecker::getLogcheckerVersion(), $logfile->detailsAsString()
);
$LogID = $db->inserted_id();
$ripFiler->put($logfile->filepath(), [$TorrentID, $LogID]);
$htmlFiler->put($logfile->text(), [$TorrentID, $LogID]);
}
$torrent->updateLogScore($logfileSummary);
} }
$torrent->modifyLogscore();
} }
// Update info for the torrent // Update info for the torrent
@@ -303,5 +281,4 @@ $changeLog = implode(', ', $change);
. $Viewer->username() . " ($changeLog)"); . $Viewer->username() . " ($changeLog)");
$torrent->flush(); $torrent->flush();
$Cache->delete_value("torrent_download_$TorrentID");
header("Location: " . $torrent->location()); header("Location: " . $torrent->location());

View File

@@ -24,18 +24,18 @@ $Properties['Title'] = isset($_POST['title']) ? trim($_POST['title']) : null;
// Remastered is an Enum in the DB // Remastered is an Enum in the DB
$Properties['Remastered'] = !empty($_POST['remaster']) ? '1' : '0'; $Properties['Remastered'] = !empty($_POST['remaster']) ? '1' : '0';
if ($Properties['Remastered'] || !empty($_POST['unknown'])) { if ($Properties['Remastered'] || !empty($_POST['unknown'])) {
$Properties['UnknownRelease'] = !empty($_POST['unknown']) ? 1 : 0; $Properties['UnknownRelease'] = !empty($_POST['unknown']) ? 1 : 0;
$Properties['RemasterYear'] = isset($_POST['remaster_year']) ? (int)$_POST['remaster_year'] : null; $Properties['RemasterYear'] = isset($_POST['remaster_year']) ? (int)$_POST['remaster_year'] : null;
$_POST['remaster_year'] = $Properties['RemasterYear']; $_POST['remaster_year'] = $Properties['RemasterYear'];
$Properties['RemasterTitle'] = trim($_POST['remaster_title'] ?? ''); $Properties['RemasterTitle'] = trim($_POST['remaster_title'] ?? '');
$Properties['RemasterRecordLabel'] = trim($_POST['remaster_record_label'] ?? ''); $Properties['RemasterRecordLabel'] = trim($_POST['remaster_record_label'] ?? '');
$Properties['RemasterCatalogueNumber'] = trim($_POST['remaster_catalogue_number'] ?? ''); $Properties['RemasterCatalogueNumber'] = trim($_POST['remaster_catalogue_number'] ?? '');
} }
if (!$Properties['Remastered'] || $Properties['UnknownRelease']) { if (!$Properties['Remastered'] || $Properties['UnknownRelease']) {
$Properties['UnknownRelease'] = 1; $Properties['UnknownRelease'] = 1;
$Properties['RemasterYear'] = null; $Properties['RemasterYear'] = null;
$Properties['RemasterTitle'] = ''; $Properties['RemasterTitle'] = '';
$Properties['RemasterRecordLabel'] = ''; $Properties['RemasterRecordLabel'] = '';
$Properties['RemasterCatalogueNumber'] = ''; $Properties['RemasterCatalogueNumber'] = '';
} }
$Properties['Year'] = isset($_POST['year']) ? (int)$_POST['year'] : null; $Properties['Year'] = isset($_POST['year']) ? (int)$_POST['year'] : null;
@@ -472,24 +472,6 @@ if ($Err) {
} }
} }
//******************************************************************************//
//--------------- Start database stuff -----------------------------------------//
$logfileSummary = new Gazelle\LogfileSummary;
if ($HasLog == '1' && isset($_FILES['logfiles'])) {
foreach (array_keys($_FILES['logfiles']['error']) as $n) {
if ($_FILES['logfiles']['error'][$n] == UPLOAD_ERR_OK) {
$logfileSummary->add(
new Gazelle\Logfile(
$_FILES['logfiles']['tmp_name'][$n],
$_FILES['logfiles']['name'][$n]
)
);
}
}
}
$LogInDB = count($logfileSummary->all()) ? '1' : '0';
$tgroup = null; $tgroup = null;
$NoRevision = false; $NoRevision = false;
if ($isMusicUpload) { if ($isMusicUpload) {
@@ -527,7 +509,11 @@ $IsNewGroup = is_null($tgroup);
//Needs to be here as it isn't set for add format until now //Needs to be here as it isn't set for add format until now
$LogName .= $Properties['Title']; $LogName .= $Properties['Title'];
//----- Start inserts $logfileSummary = new Gazelle\LogfileSummary($_FILES['logfiles']);
$LogInDB = $logfileSummary->total() ? '1' : '0';
//******************************************************************************//
//--------------- Start database stuff -----------------------------------------//
$Debug->set_flag('upload: database begin transaction'); $Debug->set_flag('upload: database begin transaction');
$db = Gazelle\DB::DB(); $db = Gazelle\DB::DB();
@@ -613,58 +599,45 @@ $extraFile = [];
$trackerUpdate = []; $trackerUpdate = [];
foreach ($ExtraTorrentsInsert as $ExtraTorrent) { foreach ($ExtraTorrentsInsert as $ExtraTorrent) {
$db->prepared_query(" $torrentExtra = $torMan->create(
INSERT INTO torrents tgroupId: $GroupID,
(GroupID, UserID, Media, Format, Encoding, userId: $Viewer->id(),
Remastered, RemasterYear, RemasterTitle, RemasterRecordLabel, RemasterCatalogueNumber, description: $ExtraTorrent['TorrentDescription'],
info_hash, FileCount, FileList, FilePath, Size, Description, media: $Properties['Media'],
Time, LogScore, HasLog, HasCue, FreeTorrent, FreeLeechType) format: $ExtraTorrent['Format'],
VALUES encoding: $ExtraTorrent['Encoding'],
(?, ?, ?, ?, ?, infohash: $ExtraTorrent['InfoHash'],
?, ?, ?, ?, ?, filePath: $ExtraTorrent['FilePath'],
?, ?, ?, ?, ?, ?, fileList: $ExtraTorrent['FileString'],
now(), 0, '0', '0', '0', '0') size: $ExtraTorrent['TotalSize'],
", $GroupID, $Viewer->id(), $Properties['Media'], $ExtraTorrent['Format'], $ExtraTorrent['Encoding'], isScene: $Properties['Scene'],
$Properties['Remastered'], $Properties['RemasterYear'], $Properties['RemasterTitle'], $Properties['RemasterRecordLabel'], $Properties['RemasterCatalogueNumber'], isRemaster: $Properties['Remastered'],
$ExtraTorrent['InfoHash'], $ExtraTorrent['NumFiles'], $ExtraTorrent['FileString'], remasterYear: $Properties['RemasterYear'],
$ExtraTorrent['FilePath'], $ExtraTorrent['TotalSize'], $ExtraTorrent['TorrentDescription'] remasterTitle: $Properties['RemasterTitle'],
remasterRecordLabel: $Properties['RemasterRecordLabel'],
remasterCatalogueNumber: $Properties['RemasterCatalogueNumber'],
); );
$ExtraTorrentID = $db->inserted_id();
$db->prepared_query('
INSERT INTO torrents_leech_stats (TorrentID)
VALUES (?)
', $ExtraTorrentID
);
$torrentExtra = new Gazelle\Torrent($ExtraTorrentID);
$torMan->flushFoldernameCache($ExtraTorrent['FilePath']); $torMan->flushFoldernameCache($torrentExtra->path());
$folderCheck[] = $ExtraTorrent['FilePath']; $folderCheck[] = $torrentExtra->path();
$trackerUpdate[$ExtraTorrentID] = rawurlencode($ExtraTorrent['InfoHash']); $trackerUpdate[$torrentExtra->id()] = rawurlencode($torrentExtra->infohash());
$bonusTotal += $bonus->torrentValue($torrentExtra); $bonusTotal += $bonus->torrentValue($torrentExtra);
$extraFile[$ExtraTorrentID] = [ $extraFile[$torrentExtra->id()] = [
'payload' => $ExtraTorrent['TorEnc'], 'payload' => $ExtraTorrent['TorEnc'],
'size' => number_format($ExtraTorrent['TotalSize'] / (1024 * 1024), 2) 'size' => number_format($torrentExtra->size() / (1024 * 1024), 2)
]; ];
} }
//******************************************************************************// //******************************************************************************//
//--------------- Write Files To Disk ------------------------------------------// //--------------- Write Files To Disk ------------------------------------------//
$ripFiler = new Gazelle\File\RipLog; if ($logfileSummary->total()) {
$htmlFiler = new Gazelle\File\RipLogHTML; $torrentLogManager = new Gazelle\Manager\TorrentLog(new Gazelle\File\RipLog, new Gazelle\File\RipLogHTML);
foreach($logfileSummary->all() as $logfile) { $checkerVersion = Logchecker::getLogcheckerVersion();
$db->prepared_query(' foreach($logfileSummary->all() as $logfile) {
INSERT INTO torrents_logs $torrentLogManager->create($torrent, $logfile, $checkerVersion);
(TorrentID, Score, `Checksum`, FileName, Ripper, RipperVersion, `Language`, ChecksumState, LogcheckerVersion, Details) }
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
', $TorrentID, $logfile->score(), $logfile->checksumStatus(), $logfile->filename(),
$logfile->ripper(), $logfile->ripperVersion(), $logfile->language(), $logfile->checksumState(),
Logchecker::getLogcheckerVersion(), $logfile->detailsAsString()
);
$LogID = $db->inserted_id();
$ripFiler->put($logfile->filepath(), [$TorrentID, $LogID]);
$htmlFiler->put($logfile->text(), [$TorrentID, $LogID]);
} }
$log = new Gazelle\Log; $log = new Gazelle\Log;
@@ -675,7 +648,7 @@ $log->torrent($GroupID, $TorrentID, $Viewer->id(), 'uploaded ('.number_format($T
foreach ($extraFile as $id => $info) { foreach ($extraFile as $id => $info) {
$torrentFiler->put($info['payload'], $id); $torrentFiler->put($info['payload'], $id);
$log->torrent($GroupID, $id, $Viewer->id(), "uploaded ({$info['size']} MiB)") $log->torrent($GroupID, $id, $Viewer->id(), "uploaded ({$info['size']} MiB)")
->general("Torrent $ExtraTorrentID ($LogName) ({$info['size']} MiB) was uploaded by " . $Viewer->username()); ->general("Torrent $id ($LogName) ({$info['size']} MiB) was uploaded by " . $Viewer->username());
} }
$db->commit(); // We have a usable upload, any subsequent failures can be repaired ex post facto $db->commit(); // We have a usable upload, any subsequent failures can be repaired ex post facto

View File

@@ -16,7 +16,7 @@
{% if viewer.permitted('users_mod') %} {% if viewer.permitted('users_mod') %}
<div style="padding: 10px; float: right"><a class="brackets" href="torrents.php?action=editlog&amp;torrentid={{ id }}&amp;logid={{ log.id }}">Edit Log</a> <div style="padding: 10px; float: right"><a class="brackets" href="torrents.php?action=editlog&amp;torrentid={{ id }}&amp;logid={{ log.id }}">Edit Log</a>
<a class="brackets" onclick="return confirm('Are you sure you want to deleted this log? There is NO undo!');" <a class="brackets" onclick="return confirm('Are you sure you want to deleted this log? There is NO undo!');"
href="torrents.php?action=deletelog&amp;torrentid={{ id }}&amp;logid={{ log.id }}">Delete Log</a> href="torrents.php?action=deletelog&amp;torrentid={{ id }}&amp;logid={{ log.id }}&amp;auth={{ viewer.auth }}">Delete Log</a>
</div> </div>
{% endif %} {% endif %}
@@ -71,6 +71,6 @@
{% else %} {% else %}
No logs found! No logs found!
{% if viewer.permitted('torrents_delete') %} {% if viewer.permitted('torrents_delete') %}
&nbsp;<a class="brackets" href="torrents.php?action=removelogs&amp;torrentid={{ id }}">Repair DB</a> &nbsp;<a class="brackets" href="torrents.php?action=removelogs&amp;torrentid={{ id }}&amp;auth={{ viewer.auth }}">Repair DB</a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}

View File

@@ -31,7 +31,6 @@ describe('page loads as admin', () => {
"/top10.php", "/top10.php",
"/torrents.php", "/torrents.php",
"/torrents.php?action=advanced&artistname=doesnotexist", "/torrents.php?action=advanced&artistname=doesnotexist",
"/upload.php",
"/user.php", "/user.php",
"/user.php?id=1", "/user.php?id=1",
"/user.php?action=edit&id=1", "/user.php?action=edit&id=1",
@@ -48,7 +47,7 @@ describe('page loads as admin', () => {
}) })
it(`should have a footer: ${url}`, () => { it(`should have a footer: ${url}`, () => {
cy.visit(url); cy.visit(url);
cy.contains(`Site and design © ${date.getFullYear()} Gazelle`); cy.ensureFooter();
}) })
}) })
}) })

View File

@@ -1,13 +1,11 @@
describe('uploading torrent', () => { describe('uploading torrent', () => {
let date = new Date();
let footer = `Site and design © ${date.getFullYear()} Gazelle`;
beforeEach(() => { beforeEach(() => {
cy.loginUser(); cy.loginUser();
}) })
it('upload music torrent', () => { it('upload music torrent', () => {
cy.visit('/upload.php'); cy.visit('/upload.php');
cy.ensureFooter();
cy.get('#file').selectFile('tests/cypress/files/valid_torrent.torrent') cy.get('#file').selectFile('tests/cypress/files/valid_torrent.torrent')
cy.get("#categories").select('Music'); cy.get("#categories").select('Music');
cy.get("#releasetype").select('Album'); cy.get("#releasetype").select('Album');
@@ -41,11 +39,12 @@ describe('uploading torrent', () => {
cy.contains('Some Artist'); cy.contains('Some Artist');
cy.contains('2022'); cy.contains('2022');
cy.contains('Cool Test Label!'); cy.contains('Cool Test Label!');
cy.contains(footer); cy.ensureFooter();
}) })
it('upload torrent to existing group', () => { it('upload torrent to existing group', () => {
cy.visit('/upload.php'); cy.visit('/upload.php');
cy.ensureFooter();
cy.get('#file').selectFile('tests/cypress/files/valid_torrent_2.torrent') cy.get('#file').selectFile('tests/cypress/files/valid_torrent_2.torrent')
cy.get("#categories").select('Music'); cy.get("#categories").select('Music');
cy.get("#releasetype").select('Album'); cy.get("#releasetype").select('Album');
@@ -76,6 +75,82 @@ describe('uploading torrent', () => {
cy.contains('2022'); cy.contains('2022');
cy.contains('Cool Test Label!'); cy.contains('Cool Test Label!');
cy.contains('[CD / FLAC / Lossless / Log (100%)]'); cy.contains('[CD / FLAC / Lossless / Log (100%)]');
cy.contains(footer); cy.ensureFooter();
})
it('re-attach log to upload', () => {
// find torrent id
cy.visit('/torrents.php?type=uploaded');
cy.ensureFooter();
// bunch of callbacks have been flattened below
cy.get('.group_info').contains('Log (100%)').first()
.find('a[href*="torrents.php?"][href*="torrentid="]').first()
.invoke('attr', 'href').then((torrent_url) => {
let torrent_id = torrent_url.match(/[&?]torrentid=([0-9]+)/)[1];
cy.visit(torrent_url);
cy.ensureFooter();
cy.get(`#torrent_${torrent_id}`).contains('View log').click();
cy.get(`a[href*="view.php?type=riplog&id=${torrent_id}."]`).first()
.invoke('attr', 'href').then((log_url) => {
let log_id = log_url.match(/&id=[0-9]+\.([0-9]+)/)[1];
// delete existing log
cy.loginAdmin();
cy.visit('/');
cy.window()
.then((window) => {
cy.window().should('have.property', 'authkey')
cy.visit('/torrents.php', {qs: {
action: 'deletelog',
torrentid: torrent_id,
logid: log_id,
auth: window.authkey
}})
});
cy.loginUser();
// verify log was correctly removed
cy.visit(torrent_url);
cy.contains('Log (100%)').should('not.exist');
cy.ensureFooter();
// set up api token
cy.visit('/user.php?action=token&do=generate',
{method: 'POST', body: {token_name: 'test_reattach_log'}});
cy.ensureFooter();
// upload log
cy.get('.box2 > .pad > strong').invoke('text').then((api_key) => {
cy.fixture('../files/valid_log_eac.log', 'binary').then( (log_bin) => {
// File in binary format gets converted to blob so it can be sent as Form data
const blob = Cypress.Blob.binaryStringToBlob(log_bin, 'application/octet-stream');
const formData = new FormData();
formData.append('logfiles[]', blob, 'eac.log');
cy.request({
url: '/ajax.php',
qs: {action: 'add_log', id: torrent_id},
method: 'POST',
headers: {authorization: `token ${api_key}`,
'content-type': 'multipart/form-data'},
body: formData
}).then( (response) => {
// it's an ArrayBuffer but for some reason instanceof ArrayBuffer is false
// and its stringifyed version is "{}"
let body = typeof response.body === 'object' ?
new TextDecoder().decode(response.body) : response.body;
try {
body = JSON.parse(body);
} catch (e) {
cy.logCli("bad response body: " + body);
}
expect(body).to.have.property('status', 'success');
});
})
// verify
cy.visit(torrent_url);
cy.ensureFooter();
cy.contains('Log (100%)');
})})});
}) })
}) })

View File

@@ -24,6 +24,9 @@
// -- This will overwrite an existing command -- // -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
const date = new Date();
const footer = `Site and design © ${date.getFullYear()} Gazelle`;
Cypress.Commands.add('login', (username, password) => { Cypress.Commands.add('login', (username, password) => {
// https://www.cypress.io/blog/2021/08/04/authenticate-faster-in-tests-cy-session-command/ // https://www.cypress.io/blog/2021/08/04/authenticate-faster-in-tests-cy-session-command/
cy.session([username, password], () => { cy.session([username, password], () => {
@@ -42,3 +45,15 @@ Cypress.Commands.add('loginAdmin', () => {
Cypress.Commands.add('loginUser', () => { Cypress.Commands.add('loginUser', () => {
cy.login('user', 'password'); cy.login('user', 'password');
}) })
Cypress.Commands.add('ensureFooter', () => {
cy.contains(footer);
})
Cypress.Commands.add('logCli', (msg) => {
// somehow cypress-terminal-report doesn't pick up cy.log()
Cypress.log({
name: "logCli",
message: msg
});
})

View File

@@ -13,12 +13,14 @@
// https://on.cypress.io/configuration // https://on.cypress.io/configuration
// *********************************************************** // ***********************************************************
// Import commands.js using ES2015 syntax: require('cypress-terminal-report/src/installLogsCollector')({
xhr: {
printHeaderData: true,
printRequestData: true
}
});
import './commands' import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')
Cypress.on('fail', (err, runnable) => { Cypress.on('fail', (err, runnable) => {
/** /**
* add DOM html to error message * add DOM html to error message

View File

@@ -3098,6 +3098,16 @@ cyclist@^1.0.1:
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
cypress-terminal-report@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/cypress-terminal-report/-/cypress-terminal-report-5.0.2.tgz#923db46f06c4b32021c48da0177065ea1af2d999"
integrity sha512-YJ6HODTvxKD0FYQX2p3f1DlBRcJcMJjRh3JWZZrBte4dQdnTuIElb4jPh3zbcqsV4MJ+QwF6XyJoXybZgg6kHg==
dependencies:
chalk "^4.0.0"
fs-extra "^10.1.0"
semver "^7.3.5"
tv4 "^1.3.0"
cypress@^12.0.2: cypress@^12.0.2:
version "12.0.2" version "12.0.2"
resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.0.2.tgz#0437abf9d97ad4a972ab554968d58211b0ce4ae5" resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.0.2.tgz#0437abf9d97ad4a972ab554968d58211b0ce4ae5"
@@ -4241,6 +4251,15 @@ fs-extra@3.0.1:
jsonfile "^3.0.0" jsonfile "^3.0.0"
universalify "^0.1.0" universalify "^0.1.0"
fs-extra@^10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^6.0.1"
universalify "^2.0.0"
fs-extra@^7.0.1: fs-extra@^7.0.1:
version "7.0.1" version "7.0.1"
resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz" resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz"
@@ -5852,6 +5871,13 @@ lru-cache@^5.1.1:
dependencies: dependencies:
yallist "^3.0.2" yallist "^3.0.2"
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies:
yallist "^4.0.0"
make-dir@^1.0.0: make-dir@^1.0.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz" resolved "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz"
@@ -8124,6 +8150,13 @@ semver@^7.3.2:
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
semver@^7.3.5:
version "7.3.8"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
dependencies:
lru-cache "^6.0.0"
send@0.16.2: send@0.16.2:
version "0.16.2" version "0.16.2"
resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1"
@@ -9247,6 +9280,11 @@ tunnel-agent@^0.6.0:
dependencies: dependencies:
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
tv4@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/tv4/-/tv4-1.3.0.tgz#d020c846fadd50c855abb25ebaecc68fc10f7963"
integrity sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==
tweetnacl@^0.14.3, tweetnacl@~0.14.0: tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5" version "0.14.5"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"