mirror of
https://github.com/usetrmnl/byos_laravel.git
synced 2026-01-13 15:07:49 +00:00
feat(#16): refactor
This commit is contained in:
parent
580a5833a8
commit
cc63c8cce2
9 changed files with 148 additions and 75 deletions
|
|
@ -36,8 +36,7 @@ class ScreenGeneratorCommand extends Command
|
|||
|
||||
return 1;
|
||||
}
|
||||
|
||||
GenerateScreenJob::dispatchSync($deviceId, $markup);
|
||||
GenerateScreenJob::dispatchSync($deviceId, null, $markup);
|
||||
|
||||
$this->info('Screen generation job finished.');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Plugin;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class GeneratePluginJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly int $pluginId,
|
||||
private readonly string $markup
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$newImageUuid = CommonFunctions::generateImage($this->markup);
|
||||
|
||||
Plugin::find($this->pluginId)->update(['current_image' => $newImageUuid]);
|
||||
\Log::info("Plugin $this->pluginId: updated with new image: $newImageUuid");
|
||||
|
||||
CommonFunctions::cleanupFolder();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3,6 +3,8 @@
|
|||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Device;
|
||||
use App\Models\Plugin;
|
||||
use App\Services\ImageGenerationService;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
|
|
@ -18,6 +20,7 @@ class GenerateScreenJob implements ShouldQueue
|
|||
*/
|
||||
public function __construct(
|
||||
private readonly int $deviceId,
|
||||
private readonly ?int $pluginId,
|
||||
private readonly string $markup
|
||||
) {}
|
||||
|
||||
|
|
@ -26,11 +29,16 @@ class GenerateScreenJob implements ShouldQueue
|
|||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$newImageUuid = CommonFunctions::generateImage($this->markup);
|
||||
$newImageUuid = ImageGenerationService::generateImage($this->markup);
|
||||
|
||||
Device::find($this->deviceId)->update(['current_screen_image' => $newImageUuid]);
|
||||
\Log::info("Device $this->deviceId: updated with new image: $newImageUuid");
|
||||
|
||||
CommonFunctions::cleanupFolder();
|
||||
if ($this->pluginId) {
|
||||
// cache current image
|
||||
Plugin::find($this->pluginId)->update(['current_image' => $newImageUuid]);
|
||||
}
|
||||
|
||||
ImageGenerationService::cleanupFolder();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ class Plugin extends Model
|
|||
'data_payload' => 'json',
|
||||
'data_payload_updated_at' => 'datetime',
|
||||
'is_native' => 'boolean',
|
||||
'current_image' => 'string',
|
||||
];
|
||||
|
||||
protected static function boot()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Device;
|
||||
use App\Models\Plugin;
|
||||
|
|
@ -9,7 +9,7 @@ use Ramsey\Uuid\Uuid;
|
|||
use Spatie\Browsershot\Browsershot;
|
||||
use Wnx\SidecarBrowsershot\BrowsershotLambda;
|
||||
|
||||
class CommonFunctions
|
||||
class ImageGenerationService
|
||||
{
|
||||
public static function generateImage(string $markup): string {
|
||||
$uuid = Uuid::uuid4()->toString();
|
||||
|
|
@ -37,7 +37,7 @@ class CommonFunctions
|
|||
}
|
||||
|
||||
try {
|
||||
CommonFunctions::convertToBmpImageMagick($pngPath, $bmpPath);
|
||||
ImageGenerationService::convertToBmpImageMagick($pngPath, $bmpPath);
|
||||
} catch (\ImagickException $e) {
|
||||
throw new \RuntimeException('Failed to convert image to BMP: '.$e->getMessage(), 0, $e);
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ new class extends Component {
|
|||
try {
|
||||
$rendered = Blade::render($this->blade_code);
|
||||
foreach ($this->checked_devices as $device) {
|
||||
GenerateScreenJob::dispatchSync($device, $rendered);
|
||||
GenerateScreenJob::dispatchSync($device, null, $rendered);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->addError('error', $e->getMessage());
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
|
||||
use App\Jobs\GenerateScreenJob;
|
||||
use App\Jobs\GeneratePluginJob;
|
||||
use App\Models\Device;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
|
|
@ -47,31 +46,32 @@ Route::get('/display', function (Request $request) {
|
|||
// Get current screen image from mirror device or continue if not available
|
||||
if (! $image_uuid = $device->mirrorDevice?->current_screen_image) {
|
||||
$refreshTimeOverride = null;
|
||||
$nextPlaylistItem = $device->getNextPlaylistItem();
|
||||
// Skip if cloud proxy is enabled for the device
|
||||
if (! $device->proxy_cloud && $nextPlaylistItem) {
|
||||
$refreshTimeOverride = $nextPlaylistItem->playlist()->first()->refresh_time;
|
||||
$plugin = $nextPlaylistItem->plugin;
|
||||
if (! $device->proxy_cloud || $device->getNextPlaylistItem()) {
|
||||
$playlistItem = $device->getNextPlaylistItem();
|
||||
if ($playlistItem) {
|
||||
$refreshTimeOverride = $playlistItem->playlist()->first()->refresh_time;
|
||||
$plugin = $playlistItem->plugin;
|
||||
|
||||
// Check and update stale data if needed
|
||||
if ($plugin->isDataStale() || $plugin->current_image == null) {
|
||||
$plugin->updateDataPayload();
|
||||
// Check and update stale data if needed
|
||||
if ($plugin->isDataStale() || $plugin->current_image == null) {
|
||||
$plugin->updateDataPayload();
|
||||
|
||||
if ($plugin->render_markup) {
|
||||
$markup = Blade::render($plugin->render_markup, ['data' => $plugin->data_payload]);
|
||||
} elseif ($plugin->render_markup_view) {
|
||||
$markup = view($plugin->render_markup_view, ['data' => $plugin->data_payload])->render();
|
||||
if ($plugin->render_markup) {
|
||||
$markup = Blade::render($plugin->render_markup, ['data' => $plugin->data_payload]);
|
||||
} elseif ($plugin->render_markup_view) {
|
||||
$markup = view($plugin->render_markup_view, ['data' => $plugin->data_payload])->render();
|
||||
}
|
||||
|
||||
GenerateScreenJob::dispatchSync($device->id, $plugin->id, $markup);
|
||||
}
|
||||
|
||||
GeneratePluginJob::dispatchSync($plugin->id, $markup);
|
||||
}
|
||||
$plugin->refresh();
|
||||
|
||||
$plugin->refresh();
|
||||
|
||||
if ($plugin->current_image != null)
|
||||
{
|
||||
$nextPlaylistItem->update(['last_displayed_at' => now()]);
|
||||
$device->update(['current_screen_image' => $plugin->current_image]);
|
||||
if ($plugin->current_image != null) {
|
||||
$playlistItem->update(['last_displayed_at' => now()]);
|
||||
$device->update(['current_screen_image' => $plugin->current_image]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -198,11 +198,11 @@ Route::get('/devices', function (Request $request) {
|
|||
'friendly_id',
|
||||
'mac_address',
|
||||
'last_battery_voltage as battery_voltage',
|
||||
'last_rssi_level as rssi'
|
||||
'last_rssi_level as rssi',
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'data' => $devices
|
||||
'data' => $devices,
|
||||
]);
|
||||
})->middleware('auth:sanctum');
|
||||
|
||||
|
|
@ -217,7 +217,7 @@ Route::post('/display/update', function (Request $request) {
|
|||
|
||||
$view = Blade::render($request['markup']);
|
||||
|
||||
GenerateScreenJob::dispatchSync($deviceId, $view);
|
||||
GenerateScreenJob::dispatchSync($deviceId, null, $view);
|
||||
|
||||
response()->json([
|
||||
'message' => 'success',
|
||||
|
|
|
|||
|
|
@ -469,7 +469,7 @@ test('authenticated user can fetch their devices', function () {
|
|||
]);
|
||||
});
|
||||
|
||||
test('plugin doesn\'t update image unless required', function () {
|
||||
test('plugin caches image until data is stale', function () {
|
||||
// Create source device with a playlist
|
||||
$device = Device::factory()->create([
|
||||
'mac_address' => '55:11:22:33:44:55',
|
||||
|
|
@ -479,11 +479,11 @@ test('plugin doesn\'t update image unless required', function () {
|
|||
|
||||
$plugin = Plugin::factory()->create([
|
||||
'name' => 'Zen Quotes',
|
||||
'polling_url' => 'https://zenquotes.io/api/today',
|
||||
'polling_url' => null,
|
||||
'data_stale_minutes' => 1,
|
||||
'data_strategy' => 'polling',
|
||||
'polling_verb' => 'get',
|
||||
'render_markup_view' => 'recipes.zen',
|
||||
'render_markup_view' => 'trmnl',
|
||||
'is_native' => false,
|
||||
'data_payload_updated_at' => null,
|
||||
]);
|
||||
|
|
@ -543,3 +543,107 @@ test('plugin doesn\'t update image unless required', function () {
|
|||
expect($thirdResponse['filename'])
|
||||
->not->toBe($firstResponse['filename']);
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
test('plugins in playlist are rendered in order', function () {
|
||||
// Create source device with a playlist
|
||||
$device = Device::factory()->create([
|
||||
'mac_address' => '55:11:22:33:44:55',
|
||||
'api_key' => 'source-api-key',
|
||||
'proxy_cloud' => true,
|
||||
]);
|
||||
|
||||
// Create two plugins
|
||||
$firstPlugin = Plugin::factory()->create([
|
||||
'name' => 'First Plugin',
|
||||
'polling_url' => null,
|
||||
'data_stale_minutes' => 1,
|
||||
'data_strategy' => 'polling',
|
||||
'polling_verb' => 'get',
|
||||
'render_markup_view' => 'trmnl',
|
||||
'is_native' => false,
|
||||
'data_payload_updated_at' => null,
|
||||
]);
|
||||
|
||||
$secondPlugin = Plugin::factory()->create([
|
||||
'name' => 'Second Plugin',
|
||||
'polling_url' => null,
|
||||
'data_stale_minutes' => 1,
|
||||
'data_strategy' => 'polling',
|
||||
'polling_verb' => 'get',
|
||||
'render_markup_view' => 'trmnl',
|
||||
'is_native' => false,
|
||||
'data_payload_updated_at' => null,
|
||||
]);
|
||||
|
||||
// Create playlist
|
||||
$playlist = Playlist::factory()->create([
|
||||
'device_id' => $device->id,
|
||||
'name' => 'Two Plugins Test',
|
||||
'is_active' => true,
|
||||
'weekdays' => null,
|
||||
'active_from' => null,
|
||||
'active_until' => null,
|
||||
]);
|
||||
|
||||
// Add plugins to playlist in specific order
|
||||
PlaylistItem::factory()->create([
|
||||
'playlist_id' => $playlist->id,
|
||||
'plugin_id' => $firstPlugin->id,
|
||||
'order' => 1,
|
||||
'is_active' => true,
|
||||
'last_displayed_at' => null,
|
||||
]);
|
||||
|
||||
PlaylistItem::factory()->create([
|
||||
'playlist_id' => $playlist->id,
|
||||
'plugin_id' => $secondPlugin->id,
|
||||
'order' => 2,
|
||||
'is_active' => true,
|
||||
'last_displayed_at' => null,
|
||||
]);
|
||||
|
||||
// First request should show the first plugin
|
||||
$firstResponse = $this->withHeaders([
|
||||
'id' => $device->mac_address,
|
||||
'access-token' => $device->api_key,
|
||||
])->get('/api/display');
|
||||
|
||||
$firstResponse->assertOk();
|
||||
$firstImageFilename = $firstResponse['filename'];
|
||||
expect($firstImageFilename)->not->toBe('setup-logo.bmp');
|
||||
|
||||
// Get the first plugin's playlist item and verify it was marked as displayed
|
||||
$firstPluginItem = PlaylistItem::where('plugin_id', $firstPlugin->id)->first();
|
||||
expect($firstPluginItem->last_displayed_at)->not->toBeNull();
|
||||
|
||||
// Second request should show the second plugin
|
||||
$secondResponse = $this->withHeaders([
|
||||
'id' => $device->mac_address,
|
||||
'access-token' => $device->api_key,
|
||||
'rssi' => -70,
|
||||
'battery_voltage' => 3.8,
|
||||
'fw-version' => '1.0.0',
|
||||
])->get('/api/display');
|
||||
|
||||
$secondResponse->assertOk();
|
||||
expect($secondResponse['filename'])
|
||||
->not->toBe($firstImageFilename)
|
||||
->not->toBe('setup-logo.bmp');
|
||||
|
||||
// Get the second plugin's playlist item and verify it was marked as displayed
|
||||
$secondPluginItem = PlaylistItem::where('plugin_id', $secondPlugin->id)->first();
|
||||
expect($secondPluginItem->last_displayed_at)->not->toBeNull();
|
||||
|
||||
// Third request should show the first plugin again
|
||||
$thirdResponse = $this->withHeaders([
|
||||
'id' => $device->mac_address,
|
||||
'access-token' => $device->api_key,
|
||||
'rssi' => -70,
|
||||
'battery_voltage' => 3.8,
|
||||
'fw-version' => '1.0.0',
|
||||
])->get('/api/display');
|
||||
|
||||
$thirdResponse->assertOk();
|
||||
expect($thirdResponse['filename'])
|
||||
->not->toBe($secondResponse['filename']);
|
||||
})->skipOnGitHubActions();
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ beforeEach(function () {
|
|||
|
||||
test('it generates screen images and updates device', function () {
|
||||
$device = Device::factory()->create();
|
||||
$job = new GenerateScreenJob($device->id, view('trmnl')->render());
|
||||
$job = new GenerateScreenJob($device->id, null, view('trmnl')->render());
|
||||
$job->handle();
|
||||
|
||||
// Assert the device was updated with a new image UUID
|
||||
|
|
@ -39,7 +39,7 @@ test('it cleans up unused images', function () {
|
|||
Storage::disk('public')->put('/images/generated/inactive-uuid.bmp', 'test');
|
||||
|
||||
// Run a job which will trigger cleanup
|
||||
$job = new GenerateScreenJob($activeDevice->id, '<div>Test</div>');
|
||||
$job = new GenerateScreenJob($activeDevice->id, null, '<div>Test</div>');
|
||||
$job->handle();
|
||||
|
||||
Storage::disk('public')->assertMissing('/images/generated/uuid-to-be-replaced.png');
|
||||
|
|
@ -52,7 +52,7 @@ test('it preserves gitignore file during cleanup', function () {
|
|||
Storage::disk('public')->put('/images/generated/.gitignore', '*');
|
||||
|
||||
$device = Device::factory()->create();
|
||||
$job = new GenerateScreenJob($device->id, '<div>Test</div>');
|
||||
$job = new GenerateScreenJob($device->id, null, '<div>Test</div>');
|
||||
$job->handle();
|
||||
|
||||
Storage::disk('public')->assertExists('/images/generated/.gitignore');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue