mirror of
https://github.com/anikeen-com/print-cli.git
synced 2026-03-13 13:46:07 +00:00
Add autowire
Add logs command Improve print jobs handling Improve installation/documentation
This commit is contained in:
90
app/Commands/Autowire.php
Normal file
90
app/Commands/Autowire.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace App\Commands;
|
||||
|
||||
use App\Support\PrinterManager;
|
||||
use App\Traits\HasConfig;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use LaravelZero\Framework\Commands\Command;
|
||||
use Smalot\Cups\Model\Printer;
|
||||
use Throwable;
|
||||
|
||||
class Autowire extends Command
|
||||
{
|
||||
use HasConfig;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'autowire {--init : Initialize the configuration when no config file exists}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Automatically wire up the application';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(PrinterManager $manager): int
|
||||
{
|
||||
$this->info('Autowiring the application...');
|
||||
|
||||
$printers = $manager->getList()->map(fn(Printer $printer) => [
|
||||
'name' => $printer->getName(),
|
||||
'status' => $printer->getStatus(),
|
||||
'attributes' => $printer->getAttributes(),
|
||||
'uri' => $printer->getUri(),
|
||||
]);
|
||||
|
||||
try {
|
||||
if ($this->option('init') && !file_exists($this->getYamlFilename())) {
|
||||
$response = Http::acceptJson()->post('https://events.anikeen.com/api/printers/autoinit', [
|
||||
'hostname' => gethostname(),
|
||||
]);
|
||||
|
||||
if ($response->successful()) {
|
||||
$this->setConfig($response->json());
|
||||
} else {
|
||||
$this->warn('Unable to auto initialize configuration! Please use `print-cli init` to create a configuration file.');
|
||||
}
|
||||
}
|
||||
|
||||
$contig = $this->getConfig(expectedVersion: 2);
|
||||
$response = Http::acceptJson()->patch(sprintf('%s/api/printers/autowire', $contig->getBaseUrl()), [
|
||||
'id' => $contig->getId(),
|
||||
'hostname' => gethostname(),
|
||||
'printers' => $printers->toArray(),
|
||||
]);
|
||||
|
||||
if ($response->successful()) {
|
||||
$newPrinters = $response->json('printers', []);
|
||||
if (empty($newPrinters)) {
|
||||
$this->info('No new printers found to autowire.');
|
||||
} else {
|
||||
foreach ($newPrinters as $printer) {
|
||||
$this->info(sprintf('Printer: %s', $printer['uri']));
|
||||
}
|
||||
}
|
||||
$this->setConfig([
|
||||
...$contig->toArray(),
|
||||
'printers' => $newPrinters,
|
||||
]);
|
||||
} else {
|
||||
throw new Exception('Failed to autowire printers: ' . $response->body());
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
$this->error('Failed to autowire printers: ' . $exception->getMessage());
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$this->info('Autowiring completed successfully.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,18 @@
|
||||
|
||||
namespace App\Commands;
|
||||
|
||||
use App\Traits\HasConfig;
|
||||
use Illuminate\Support\Collection;
|
||||
use LaravelZero\Framework\Commands\Command;
|
||||
use Smalot\Cups\Builder\Builder;
|
||||
use Smalot\Cups\Manager\PrinterManager;
|
||||
use Smalot\Cups\Model\Printer;
|
||||
use Smalot\Cups\Transport\Client;
|
||||
use Smalot\Cups\Transport\ResponseParser;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class InitCommand extends Command
|
||||
class Init extends Command
|
||||
{
|
||||
use HasConfig;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
@@ -33,7 +34,6 @@ class InitCommand extends Command
|
||||
public function handle(): void
|
||||
{
|
||||
$username = getenv('USER') ?: get_current_user();
|
||||
$home = getenv('HOME');
|
||||
|
||||
$client = new Client();
|
||||
$builder = new Builder();
|
||||
@@ -48,8 +48,10 @@ class InitCommand extends Command
|
||||
return;
|
||||
}
|
||||
|
||||
$this->info('Please register printers first at:');
|
||||
$this->info('https://events.anikeen.com/console/resources/printers');
|
||||
$this->info('Please register the print-server first at:');
|
||||
$this->info('https://events.anikeen.com/console/resources/print-servers');
|
||||
|
||||
$id = $this->ask('What is your print-server ID?');
|
||||
|
||||
$password = $this->ask('What is your password?', match ($username) {
|
||||
'print-cli' => 'print-cli',
|
||||
@@ -57,36 +59,14 @@ class InitCommand extends Command
|
||||
default => null,
|
||||
});
|
||||
|
||||
$filename = $home . '/print-cli.yml';
|
||||
|
||||
$yaml = Yaml::dump(
|
||||
input: [
|
||||
'base_url' => 'https://events.anikeen.com',
|
||||
'printers' => $printers->map(function (Printer $printer) use ($username, $password) {
|
||||
$attributes = $printer->getAttributes();
|
||||
|
||||
$id = $this->ask(sprintf('What is your ID for %s?', $printer->getName()));
|
||||
|
||||
return [
|
||||
'id' => $id,
|
||||
'name' => $printer->getName(),
|
||||
'driver' => 'cups',
|
||||
'address' => $attributes['printer-uri-supported'][0],
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
];
|
||||
})->toArray(),
|
||||
$this->setConfig([
|
||||
'version' => 2,
|
||||
'id' => $id,
|
||||
'base_url' => 'https://events.anikeen.com',
|
||||
'default_credentials' => [
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
],
|
||||
inline: 100,
|
||||
indent: 2,
|
||||
flags: Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK
|
||||
);
|
||||
|
||||
file_put_contents(
|
||||
filename: $filename,
|
||||
data: preg_replace('/-\n\s+/', '- ', $yaml)
|
||||
);
|
||||
|
||||
$this->info(sprintf('Created configuration at %s', $filename));
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ namespace App\Commands;
|
||||
|
||||
use LaravelZero\Framework\Commands\Command;
|
||||
|
||||
class InitSupervisorCommand extends Command
|
||||
class InitSupervisor extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
@@ -34,8 +34,8 @@ directory = $home
|
||||
command = /usr/bin/php $home/.config/composer/vendor/bin/print-cli serve
|
||||
autostart = true
|
||||
autorestart = true
|
||||
stderr_logfile = /var/log/print-cli.err.log
|
||||
stdout_logfile = /var/log/print-cli.out.log
|
||||
redirect_stderr = true
|
||||
stdout_logfile = /var/log/print-cli.log
|
||||
stopwaitsecs = 3600
|
||||
user = $username
|
||||
INI;
|
||||
32
app/Commands/InitWireguard.php
Normal file
32
app/Commands/InitWireguard.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Commands;
|
||||
|
||||
use App\Support\PrinterManager;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use LaravelZero\Framework\Commands\Command;
|
||||
|
||||
class InitWireguard extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'init:wireguard';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Initialize WireGuard configuration';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(PrinterManager $printer)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
64
app/Commands/Logs.php
Normal file
64
app/Commands/Logs.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Commands;
|
||||
|
||||
use App\Traits\HasConfig;
|
||||
use LaravelZero\Framework\Commands\Command;
|
||||
|
||||
class Logs extends Command
|
||||
{
|
||||
use HasConfig;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'logs';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Display the print server logs in real-time';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$logFile = '/var/log/print-cli.log';
|
||||
|
||||
if (!file_exists($logFile)) {
|
||||
$this->error('Log file not found. Please ensure the print server is running.');
|
||||
return;
|
||||
}
|
||||
$cmd = "tail -f $logFile";
|
||||
$descriptorspec = [
|
||||
1 => ['pipe', 'w'], // stdout
|
||||
2 => ['pipe', 'w'], // stderr
|
||||
];
|
||||
$process = proc_open($cmd, $descriptorspec, $pipes);
|
||||
|
||||
if (is_resource($process)) {
|
||||
stream_set_blocking($pipes[1], false);
|
||||
stream_set_blocking($pipes[2], false);
|
||||
|
||||
while (true) {
|
||||
$out = stream_get_contents($pipes[1]);
|
||||
$err = stream_get_contents($pipes[2]);
|
||||
|
||||
if ($out) $this->info(trim($out));
|
||||
if ($err) $this->error(trim($err));
|
||||
|
||||
usleep(200000); // 200ms delay to avoid CPU overuse
|
||||
}
|
||||
|
||||
// Never reached, but if you need to stop manually:
|
||||
// fclose($pipes[1]);
|
||||
// fclose($pipes[2]);
|
||||
// proc_close($process);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,10 @@
|
||||
|
||||
namespace App\Commands;
|
||||
|
||||
use App\Support\PrinterManager;
|
||||
use LaravelZero\Framework\Commands\Command;
|
||||
use Smalot\Cups\Builder\Builder;
|
||||
use Smalot\Cups\Manager\PrinterManager;
|
||||
use Smalot\Cups\Transport\Client;
|
||||
use Smalot\Cups\Transport\ResponseParser;
|
||||
|
||||
class ListCommand extends Command
|
||||
class Printers extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
@@ -26,20 +23,17 @@ class ListCommand extends Command
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle(): int
|
||||
public function handle(PrinterManager $printers): int
|
||||
{
|
||||
$client = new Client();
|
||||
$builder = new Builder();
|
||||
$responseParser = new ResponseParser();
|
||||
$printers = $printers->getList();
|
||||
|
||||
$printerManager = new PrinterManager($builder, $client, $responseParser);
|
||||
if ($printers->isEmpty()) {
|
||||
$this->error('We could not find any printers! Please register them first in CUPS.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$printers = $printerManager->getList();
|
||||
|
||||
$this->info('Printers:');
|
||||
$this->info('Printer:');
|
||||
|
||||
foreach ($printers as $printer) {
|
||||
$this->info($printer->getName());
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
namespace App\Commands;
|
||||
|
||||
use App\Support\Config;
|
||||
use App\Traits\HasConfig;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Http\Client\RequestException;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -15,11 +16,12 @@ use Smalot\Cups\Manager\PrinterManager;
|
||||
use Smalot\Cups\Model\Job;
|
||||
use Smalot\Cups\Transport\Client;
|
||||
use Smalot\Cups\Transport\ResponseParser;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Throwable;
|
||||
|
||||
class ServeCommand extends Command
|
||||
class Serve extends Command
|
||||
{
|
||||
use HasConfig;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
@@ -34,18 +36,80 @@ class ServeCommand extends Command
|
||||
*/
|
||||
protected $description = 'Command description';
|
||||
|
||||
private $counter = 0;
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
$this->serve();
|
||||
} catch (Throwable $e) {
|
||||
if ($e instanceof RequestException && $e->response->status() === 422) {
|
||||
$this->error('Please check your configuration and try again.');
|
||||
$this->error($e->response->json()['message']);
|
||||
} else {
|
||||
$this->info('Starting service...');
|
||||
$this->monitor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitor the service and handle jobs.
|
||||
*/
|
||||
private function monitor(): void
|
||||
{
|
||||
while (true) {
|
||||
// autowire every 10 iterations
|
||||
if ($this->counter % 10 === 0) {
|
||||
try {
|
||||
$this->call('autowire', [
|
||||
'--init' => $this->counter === 0, // Initialize only on the first run
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
$this->error('Autowire failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$config = $this->getConfig(expectedVersion: 2);
|
||||
$this->fetchJobs($config);
|
||||
sleep(2);
|
||||
} catch (ConnectionException $e) {
|
||||
$this->error('Connection error: ' . $e->getMessage());
|
||||
sleep(2);
|
||||
} catch (RequestException $e) {
|
||||
$this->error('Request error: ' . $e->getMessage());
|
||||
sleep(2);
|
||||
} catch (Throwable $e) {
|
||||
$this->error('An unexpected error occurred: ' . $e->getMessage());
|
||||
sleep(2);
|
||||
}
|
||||
|
||||
$this->counter++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RequestException
|
||||
* @throws ConnectionException
|
||||
*/
|
||||
private function fetchJobs(Config $config): void
|
||||
{
|
||||
$printerIds = $config->getPrinters()->pluck('id')->toArray();
|
||||
$response = Http::acceptJson()->get(sprintf('%s/api/printers/jobs', $config->getBaseUrl()), [
|
||||
'printer_ids' => $printerIds,
|
||||
]);
|
||||
|
||||
if ($response->failed()) {
|
||||
throw new RequestException($response);
|
||||
}
|
||||
|
||||
$jobs = $response->json();
|
||||
|
||||
if (empty($jobs)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->info('Printing jobs...');
|
||||
|
||||
foreach ($jobs as $job) {
|
||||
try {
|
||||
$this->handleJob($config, $job);
|
||||
} catch (Throwable $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
@@ -55,80 +119,18 @@ class ServeCommand extends Command
|
||||
* @throws RequestException
|
||||
* @throws ConnectionException
|
||||
*/
|
||||
private function serve(): void
|
||||
private function handleJob(Config $config, array $job): void
|
||||
{
|
||||
$this->info('Starting service...');
|
||||
$printer = $config->getPrinters()->firstWhere('id', $job['printer_id']);
|
||||
[$username, $password] = $this->getConfig()->getPrinterCredentials($printer);
|
||||
|
||||
$yaml = $this->getConfiguration();
|
||||
$printerIds = array_map(fn($printer) => $printer['id'], $yaml['printers']);
|
||||
|
||||
$response = Http::acceptJson()->patch(sprintf('%s/api/printers/register', $yaml['base_url']), [
|
||||
'printers' => $yaml['printers'],
|
||||
]);
|
||||
|
||||
$response->throw();
|
||||
|
||||
$this->info('Service started!');
|
||||
|
||||
do {
|
||||
try {
|
||||
$response = Http::acceptJson()->get(sprintf('%s/api/printers/jobs', $yaml['base_url']), [
|
||||
'printer_ids' => $printerIds,
|
||||
]);
|
||||
|
||||
if ($response->failed()) {
|
||||
$this->error('Failed to fetch jobs, error: ' . $response->status());
|
||||
sleep(2);
|
||||
continue;
|
||||
}
|
||||
|
||||
$jobs = $response->json();
|
||||
|
||||
if (empty($jobs)) {
|
||||
sleep(2);
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->info('Printing jobs...');
|
||||
|
||||
foreach ($jobs as $job) {
|
||||
try {
|
||||
$this->handleJob($job, $yaml);
|
||||
} catch (Throwable $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
sleep(2);
|
||||
} catch (Throwable $e) {
|
||||
$this->error($e->getMessage());
|
||||
sleep(2);
|
||||
}
|
||||
|
||||
} while (true);
|
||||
}
|
||||
|
||||
private function getConfiguration(): array
|
||||
{
|
||||
$this->info('Reading configuration...');
|
||||
$yaml = file_get_contents(getcwd() . '/print-cli.yml');
|
||||
return Yaml::parse($yaml);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RequestException
|
||||
* @throws ConnectionException
|
||||
*/
|
||||
private function handleJob(array $job, mixed $yaml): void
|
||||
{
|
||||
$printer = Collection::make($yaml['printers'])->firstWhere('id', $job['printer_id']);
|
||||
if (!empty($job['data']['preview'])) {
|
||||
$this->info(sprintf('Job %s is a preview', $job['id']));
|
||||
$this->markCompleted($job, $yaml, 0);
|
||||
$this->markCompleted($config, $job, 0);
|
||||
return;
|
||||
} elseif (empty($job['file_url'])) {
|
||||
$this->info(sprintf('Job %s has no file', $job['id']));
|
||||
$this->markFailed($job, $yaml, 'No file provided');
|
||||
$this->markFailed($config, $job, 'No file provided');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -138,12 +140,12 @@ class ServeCommand extends Command
|
||||
$pointWidth = round($paperWidth * 2.83465, 2);
|
||||
$pointHeight = round($paperHeight * 2.83465, 2);
|
||||
|
||||
$client = new Client($printer['username'], $printer['password']);
|
||||
$client = new Client($username, $password);
|
||||
$builder = new Builder();
|
||||
$responseParser = new ResponseParser();
|
||||
|
||||
$printerManager = new PrinterManager($builder, $client, $responseParser);
|
||||
$printer = $printerManager->findByUri($printer['address']);
|
||||
$printer = $printerManager->findByUri($printer['uri']);
|
||||
$jobManager = new JobManager($builder, $client, $responseParser);
|
||||
|
||||
$content = file_get_contents($job['file_url']);
|
||||
@@ -158,12 +160,12 @@ class ServeCommand extends Command
|
||||
$printerJob->addAttribute('fit-to-page', true);
|
||||
|
||||
if (!$jobManager->send($printer, $printerJob)) {
|
||||
$this->markFailed($job, $yaml, 'Failed to print job');
|
||||
$this->markFailed($config, $job, 'Failed to print job');
|
||||
$this->error(sprintf('Failed to print job %s', $job['id']));
|
||||
return;
|
||||
}
|
||||
|
||||
$this->markCompleted($job, $yaml, $printerJob->getId());
|
||||
$this->markCompleted($config, $job, $printerJob->getId());
|
||||
|
||||
$this->info(sprintf('Job %s completed as %s', $job['id'], $printerJob->getId()));
|
||||
}
|
||||
@@ -172,11 +174,12 @@ class ServeCommand extends Command
|
||||
* @throws RequestException
|
||||
* @throws ConnectionException
|
||||
*/
|
||||
private function markCompleted(array $job, mixed $yaml, int $jobId): void
|
||||
private function markCompleted(Config $config, array $job, int $jobId): void
|
||||
{
|
||||
$response = Http::acceptJson()->patch(sprintf('%s/api/printers/jobs/%s/complete', $yaml['base_url'], $job['id']), [
|
||||
'job_id' => $jobId,
|
||||
]);
|
||||
$response = Http::acceptJson()
|
||||
->patch(sprintf('%s/api/printers/jobs/%s/complete', $config->getBaseUrl(), $job['id']), [
|
||||
'job_id' => $jobId,
|
||||
]);
|
||||
$response->throw();
|
||||
}
|
||||
|
||||
@@ -184,11 +187,12 @@ class ServeCommand extends Command
|
||||
* @throws RequestException
|
||||
* @throws ConnectionException
|
||||
*/
|
||||
private function markFailed(array $job, mixed $yaml, string $reason): void
|
||||
private function markFailed(Config $config, array $job, string $reason): void
|
||||
{
|
||||
$response = Http::acceptJson()->patch(sprintf('%s/api/printers/jobs/%s/fail', $yaml['base_url'], $job['id']), [
|
||||
'reason' => $reason,
|
||||
]);
|
||||
$response->throw();
|
||||
Http::acceptJson()
|
||||
->patch(sprintf('%s/api/printers/jobs/%s/fail', $config->getBaseUrl(), $job['id']), [
|
||||
'reason' => $reason,
|
||||
])
|
||||
->throw();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user