Update RestoreCommand to support both async/sync requests.
This commit is contained in:
@@ -18,7 +18,6 @@ use Psr\Http\Message\StreamInterface as iStream;
|
||||
use Psr\Http\Message\UriInterface as iUri;
|
||||
use Psr\Log\LoggerInterface as iLogger;
|
||||
use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface as iResponse;
|
||||
|
||||
interface ClientInterface
|
||||
{
|
||||
@@ -86,7 +85,7 @@ interface ClientInterface
|
||||
* @param iImport $mapper mapper to use.
|
||||
* @param iDate|null $after only import items after this date.
|
||||
*
|
||||
* @return array<array-key,iResponse> responses.
|
||||
* @return array<array-key,Request> responses.
|
||||
*/
|
||||
public function pull(iImport $mapper, iDate|null $after = null): array;
|
||||
|
||||
@@ -97,7 +96,7 @@ interface ClientInterface
|
||||
* @param iStream|null $writer writer to use.
|
||||
* @param array $opts options for backup.
|
||||
*
|
||||
* @return array<array-key,iResponse> responses.
|
||||
* @return array<array-key,Request> responses.
|
||||
*/
|
||||
public function backup(iImport $mapper, iStream|null $writer = null, array $opts = []): array;
|
||||
|
||||
@@ -108,7 +107,7 @@ interface ClientInterface
|
||||
* @param QueueRequests $queue queue to use.
|
||||
* @param iDate|null $after only export items after this date.
|
||||
*
|
||||
* @return array<array-key,iResponse> responses.
|
||||
* @return array<array-key,Request> responses.
|
||||
*/
|
||||
public function export(iImport $mapper, QueueRequests $queue, iDate|null $after = null): array;
|
||||
|
||||
|
||||
@@ -5,10 +5,12 @@ declare(strict_types=1);
|
||||
namespace App\Commands\Backend;
|
||||
|
||||
use App\Command;
|
||||
use App\Libs\Attributes\DI\Inject;
|
||||
use App\Libs\Attributes\Route\Cli;
|
||||
use App\Libs\Config;
|
||||
use App\Libs\Enums\Http\Status;
|
||||
use App\Libs\Exceptions\RuntimeException;
|
||||
use App\Libs\Extends\RetryableHttpClient;
|
||||
use App\Libs\Extends\StreamLogHandler;
|
||||
use App\Libs\LogSuppressor;
|
||||
use App\Libs\Mappers\Import\RestoreMapper;
|
||||
@@ -28,6 +30,7 @@ use Symfony\Component\Console\Input\InputInterface as iInput;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface as iOutput;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface as iHttp;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
@@ -51,7 +54,9 @@ class RestoreCommand extends Command
|
||||
public function __construct(
|
||||
private readonly QueueRequests $queue,
|
||||
private readonly iLogger $logger,
|
||||
private LogSuppressor $suppressor
|
||||
private LogSuppressor $suppressor,
|
||||
#[Inject(RetryableHttpClient::class)]
|
||||
private iHttp $http,
|
||||
) {
|
||||
set_time_limit(0);
|
||||
ini_set('memory_limit', '-1');
|
||||
@@ -71,70 +76,26 @@ class RestoreCommand extends Command
|
||||
->addOption('timeout', null, InputOption::VALUE_REQUIRED, 'Set request timeout in seconds.')
|
||||
->addOption('select-backend', 's', InputOption::VALUE_REQUIRED, 'Select backend.')
|
||||
->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'Select sub user.', 'main')
|
||||
->addArgument('file', InputArgument::REQUIRED, 'Backup file to restore from')
|
||||
->addOption('logfile', null, InputOption::VALUE_REQUIRED, 'Save console output to file.')
|
||||
->setHelp(
|
||||
r(
|
||||
<<<HELP
|
||||
|
||||
This command allow you restore specific backend play state from backup file
|
||||
generated via [<cmd>state:backup</cmd>] command.
|
||||
|
||||
This restore process only works on backends that has export enabled.
|
||||
|
||||
The restore process is exactly the same as the [<cmd>state:export</cmd>] with [<flag>--ignore-date</flag>, <flag>--force-full</flag>]
|
||||
flags enabled, the difference is instead of reading state from database we are reading it from backup file.
|
||||
|
||||
-------------------
|
||||
<notice>[ Risk Assessment ]</notice>
|
||||
-------------------
|
||||
|
||||
If you are trying to restore a backend that has import play state enabled, the changes from restoring from backup file
|
||||
will propagate back to your other backends. If you don't intend for that to happen, then <fg=white;bg=red;options=bold,underscore>DISABLE</> import from the backend.
|
||||
|
||||
--------------------------------
|
||||
<notice>[ Enable restore functionality ]</notice>
|
||||
--------------------------------
|
||||
|
||||
If you understand the risks and what might happen if you do restore from a backup file,
|
||||
then you can enable the command by adding [<flag>--execute</flag>] to the command.
|
||||
|
||||
For example,
|
||||
|
||||
{cmd} <cmd>{route}</cmd> <flag>--execute</flag> <flag>-vv -s</flag> <value>backend_name</value> -- <value>{backupDir}/backup_file.json</value>
|
||||
|
||||
-------
|
||||
<notice>[ FAQ ]</notice>
|
||||
-------
|
||||
|
||||
<question># Restore operation is cancelled.</question>
|
||||
|
||||
If you encounter this error, it means either you didn't answer with yes for risk assessment confirmation,
|
||||
or the interaction is disabled, if you can't enable interaction, then you can add another flag [<flag>--assume-yes</flag>]
|
||||
to bypass the check. This <notice>confirms</notice> that you understand the risks of restoring backend that has import enabled.
|
||||
|
||||
<question># Ignoring [backend_name] [item_title]. [Movie|Episode] Is not imported yet.</question>
|
||||
|
||||
This is normal, this is likely because the backup is already outdated and some items in remote does not exist in backup file,
|
||||
or you are using backup from another source which likely does not have matching data.
|
||||
|
||||
<question># Where are the backups stored?</question>
|
||||
|
||||
By default, it should be at [<value>{backupDir}</value>].
|
||||
|
||||
<question># How to see what data will be changed?</question>
|
||||
|
||||
if you do not add [<flag>--execute</flag>] to the comment, it will run in dry mode by default,
|
||||
To see what data will be changed run the command with [<info>-v</info>]</info> log level.
|
||||
|
||||
HELP,
|
||||
[
|
||||
'cmd' => trim(commandContext()),
|
||||
'route' => self::ROUTE,
|
||||
'backupDir' => after(Config::get('path') . '/backup', ROOT_PATH),
|
||||
]
|
||||
->addOption(
|
||||
'ignore',
|
||||
'i',
|
||||
InputOption::VALUE_NONE,
|
||||
'Bypass backend export.enabled check. Use with caution.',
|
||||
)
|
||||
);
|
||||
->addArgument('file', InputArgument::REQUIRED, 'Backup file to restore from')
|
||||
->addOption(
|
||||
'sync-requests',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Send one request at a time instead of all at once. note: Slower but more reliable.'
|
||||
)
|
||||
->addOption(
|
||||
'async-requests',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Send all requests at once. note: Faster but less reliable. Default.'
|
||||
)
|
||||
->addOption('logfile', null, InputOption::VALUE_REQUIRED, 'Save console output to file.');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,6 +181,7 @@ class RestoreCommand extends Command
|
||||
}
|
||||
|
||||
if (false === (bool)ag($backend, 'export.enabled')) {
|
||||
if (false === $input->getOption('ignore')) {
|
||||
$output->writeln(r("<error>ERROR: Export to '{user}@{backend}' are disabled.</error>", [
|
||||
'backend' => $name,
|
||||
'user' => $userContext->name,
|
||||
@@ -227,6 +189,15 @@ class RestoreCommand extends Command
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$this->logger->warning(
|
||||
"Exporting to '{user}@{backend}' is disabled, However, the check was bypass due to [-i, --ignore] flag being used.",
|
||||
[
|
||||
'backend' => $name,
|
||||
'user' => $userContext->name,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if (true === (bool)ag($backend, 'import.enabled')) {
|
||||
if (false === $input->getOption('assume-yes')) {
|
||||
$helper = $this->getHelper('question');
|
||||
@@ -313,6 +284,14 @@ class RestoreCommand extends Command
|
||||
'user' => $userContext->name,
|
||||
]);
|
||||
|
||||
if (false === ($syncRequests = $input->getOption('sync-requests'))) {
|
||||
$syncRequests = (bool)Config::get('http.default.sync_requests', false);
|
||||
}
|
||||
|
||||
if (true === $input->getOption('async-requests')) {
|
||||
$syncRequests = false;
|
||||
}
|
||||
|
||||
$requests = $backend->export($mapper, $this->queue, null);
|
||||
|
||||
$start = microtime(true);
|
||||
@@ -322,14 +301,7 @@ class RestoreCommand extends Command
|
||||
'user' => $userContext->name,
|
||||
]);
|
||||
|
||||
foreach ($requests as $response) {
|
||||
$requestData = $response->getInfo('user_data');
|
||||
try {
|
||||
$requestData['ok']($response);
|
||||
} catch (Throwable $e) {
|
||||
$requestData['error']($e);
|
||||
}
|
||||
}
|
||||
send_requests(requests: $requests, client: $this->http, sync: $syncRequests, logger: $this->logger);
|
||||
|
||||
$this->logger->notice("SYSTEM: Completed '{total}' requests in '{duration}'s for '{user}@{backend}'.", [
|
||||
'backend' => $name,
|
||||
|
||||
Reference in New Issue
Block a user