refactor(export): use in-memory content for zip bundle and playlist entries
Replace file-path-based zip entries with in-memory content via file_get_contents. Rename .pro entry to 'data' (raw protobuf), add addStoredEntry() helper with CM_STORE compression, and remove temp directory management.
This commit is contained in:
parent
fa3162b2b7
commit
044b94b080
|
|
@ -24,7 +24,7 @@ public function generatePlaylist(Service $service): array
|
|||
$skippedUnmatched = $service->serviceSongs()->whereNull('song_id')->count();
|
||||
$skippedEmpty = 0;
|
||||
|
||||
$exportService = new ProExportService;
|
||||
$exportService = new ProExportService();
|
||||
$tempDir = sys_get_temp_dir().'/playlist-export-'.uniqid();
|
||||
mkdir($tempDir, 0755, true);
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ public function generatePlaylist(Service $service): array
|
|||
$destPath = $tempDir.'/'.$proFilename;
|
||||
rename($proPath, $destPath);
|
||||
|
||||
$embeddedFiles[$proFilename] = $destPath;
|
||||
$embeddedFiles[$proFilename] = file_get_contents($destPath);
|
||||
|
||||
$playlistItems[] = [
|
||||
'type' => 'presentation',
|
||||
|
|
@ -148,7 +148,7 @@ private function addSlidePresentation(
|
|||
$destPath = $tempDir.'/'.$imageFilename;
|
||||
copy($storedPath, $destPath);
|
||||
|
||||
$imageFiles[$imageFilename] = $destPath;
|
||||
$imageFiles[$imageFilename] = file_get_contents($destPath);
|
||||
|
||||
$slideDataList[] = [
|
||||
'text' => '',
|
||||
|
|
@ -182,11 +182,11 @@ private function addSlidePresentation(
|
|||
|
||||
ProFileGenerator::generateAndWrite($proPath, $label, $groups, $arrangements);
|
||||
|
||||
foreach ($imageFiles as $filename => $path) {
|
||||
$embeddedFiles[$filename] = $path;
|
||||
foreach ($imageFiles as $filename => $contents) {
|
||||
$embeddedFiles[$filename] = $contents;
|
||||
}
|
||||
|
||||
$embeddedFiles[$proFilename] = $proPath;
|
||||
$embeddedFiles[$proFilename] = file_get_contents($proPath);
|
||||
|
||||
$playlistItems[] = [
|
||||
'type' => 'presentation',
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@
|
|||
namespace App\Services;
|
||||
|
||||
use App\Models\Service;
|
||||
use InvalidArgumentException;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use InvalidArgumentException;
|
||||
use ProPresenter\Parser\ProFileGenerator;
|
||||
use RuntimeException;
|
||||
use ZipArchive;
|
||||
|
||||
class ProBundleExportService
|
||||
|
|
@ -23,12 +24,9 @@ public function generateBundle(Service $service, string $blockType): string
|
|||
->orderBy('sort_order')
|
||||
->get();
|
||||
|
||||
$tempDir = sys_get_temp_dir().'/probundle-export-'.uniqid();
|
||||
mkdir($tempDir, 0755, true);
|
||||
|
||||
$groupName = ucfirst($blockType);
|
||||
$slideData = [];
|
||||
$copiedImagePaths = [];
|
||||
$mediaFiles = [];
|
||||
|
||||
foreach ($slides as $slide) {
|
||||
$sourcePath = Storage::disk('public')->path('slides/'.$slide->stored_filename);
|
||||
|
|
@ -37,9 +35,12 @@ public function generateBundle(Service $service, string $blockType): string
|
|||
}
|
||||
|
||||
$imageFilename = basename($slide->stored_filename);
|
||||
$tempImagePath = $tempDir.'/'.$imageFilename;
|
||||
copy($sourcePath, $tempImagePath);
|
||||
$copiedImagePaths[] = $tempImagePath;
|
||||
$imageContent = file_get_contents($sourcePath);
|
||||
if ($imageContent === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mediaFiles[$imageFilename] = $imageContent;
|
||||
|
||||
$slideData[] = [
|
||||
'text' => $slide->original_filename ?? '',
|
||||
|
|
@ -63,55 +64,38 @@ public function generateBundle(Service $service, string $blockType): string
|
|||
],
|
||||
];
|
||||
|
||||
$proFilePath = $tempDir.'/'.$blockType.'.pro';
|
||||
ProFileGenerator::generateAndWrite($proFilePath, $groupName, $groups, $arrangements);
|
||||
$song = ProFileGenerator::generate($groupName, $groups, $arrangements);
|
||||
$protoBytes = $song->getPresentation()->serializeToString();
|
||||
|
||||
$bundlePath = sys_get_temp_dir().'/'.uniqid($blockType.'-').'.probundle';
|
||||
|
||||
$zip = new ZipArchive();
|
||||
$openResult = $zip->open($bundlePath, ZipArchive::CREATE | ZipArchive::OVERWRITE);
|
||||
if ($openResult !== true) {
|
||||
$this->deleteDirectory($tempDir);
|
||||
throw new InvalidArgumentException('Konnte .probundle nicht erstellen.');
|
||||
}
|
||||
|
||||
$zip->addFile($proFilePath, basename($proFilePath));
|
||||
$this->addStoredEntry($zip, 'data', $protoBytes);
|
||||
|
||||
foreach ($copiedImagePaths as $imagePath) {
|
||||
$zip->addFile($imagePath, basename($imagePath));
|
||||
foreach ($mediaFiles as $filename => $contents) {
|
||||
$this->addStoredEntry($zip, $filename, $contents);
|
||||
}
|
||||
|
||||
$zip->close();
|
||||
|
||||
$this->deleteDirectory($tempDir);
|
||||
if (! $zip->close()) {
|
||||
throw new RuntimeException('Fehler beim Finalisieren der .probundle Datei.');
|
||||
}
|
||||
|
||||
return $bundlePath;
|
||||
}
|
||||
|
||||
private function deleteDirectory(string $dir): void
|
||||
private function addStoredEntry(ZipArchive $zip, string $entryName, string $contents): void
|
||||
{
|
||||
if (! is_dir($dir)) {
|
||||
return;
|
||||
if (! $zip->addFromString($entryName, $contents)) {
|
||||
throw new RuntimeException(sprintf('Fehler beim Hinzufügen von %s zur .probundle Datei.', $entryName));
|
||||
}
|
||||
|
||||
$entries = scandir($dir);
|
||||
if ($entries === false) {
|
||||
return;
|
||||
if (! $zip->setCompressionName($entryName, ZipArchive::CM_STORE)) {
|
||||
throw new RuntimeException(sprintf('Fehler beim Setzen der Kompression für %s.', $entryName));
|
||||
}
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
if ($entry === '.' || $entry === '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = $dir.'/'.$entry;
|
||||
if (is_dir($path)) {
|
||||
$this->deleteDirectory($path);
|
||||
} else {
|
||||
@unlink($path);
|
||||
}
|
||||
}
|
||||
|
||||
@rmdir($dir);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
use App\Models\Service;
|
||||
use App\Models\Slide;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\Concerns\MakesHttpRequests;
|
||||
use Illuminate\Foundation\Testing\Concerns\InteractsWithAuthentication;
|
||||
use Illuminate\Foundation\Testing\Concerns\MakesHttpRequests;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
|
@ -15,11 +15,11 @@
|
|||
|
||||
final class ProBundleExportTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
use MakesHttpRequests;
|
||||
use InteractsWithAuthentication;
|
||||
use MakesHttpRequests;
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_probundle_enthaelt_pro_datei_und_bilder(): void
|
||||
public function test_probundle_enthaelt_data_und_bilder(): void
|
||||
{
|
||||
Storage::fake('public');
|
||||
|
||||
|
|
@ -73,14 +73,26 @@ public function test_probundle_enthaelt_pro_datei_und_bilder(): void
|
|||
$names[] = $name;
|
||||
}
|
||||
}
|
||||
$zip->close();
|
||||
|
||||
@unlink($copiedPath);
|
||||
|
||||
$this->assertContains('information.pro', $names);
|
||||
$this->assertContains('data', $names, '.probundle muss einen data-Eintrag (Protobuf) enthalten');
|
||||
$this->assertContains('info-1.jpg', $names);
|
||||
$this->assertContains('info-2.jpg', $names);
|
||||
$this->assertContains('info-3.jpg', $names);
|
||||
|
||||
// Verify media files contain actual content, not file paths
|
||||
foreach ($filenames as $index => $filename) {
|
||||
$content = $zip->getFromName($filename);
|
||||
$this->assertSame('fake-image-content-'.$index, $content, "Bildinhalt von {$filename} muss korrekt sein");
|
||||
}
|
||||
|
||||
// Verify data entry is non-empty protobuf (not a file path)
|
||||
$dataContent = $zip->getFromName('data');
|
||||
$this->assertNotFalse($dataContent, 'data-Eintrag muss existieren');
|
||||
$this->assertGreaterThan(0, strlen($dataContent), 'data-Eintrag darf nicht leer sein');
|
||||
$this->assertFalse(str_starts_with($dataContent, '/'), 'data-Eintrag darf kein Dateipfad sein');
|
||||
|
||||
$zip->close();
|
||||
@unlink($copiedPath);
|
||||
}
|
||||
|
||||
public function test_ungueltiger_block_type_liefert_422(): void
|
||||
|
|
@ -96,7 +108,7 @@ public function test_ungueltiger_block_type_liefert_422(): void
|
|||
$response->assertStatus(422);
|
||||
}
|
||||
|
||||
public function test_probundle_ohne_slides_enthaelt_nur_pro_datei(): void
|
||||
public function test_probundle_ohne_slides_enthaelt_nur_data(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$service = Service::factory()->create();
|
||||
|
|
@ -121,7 +133,7 @@ public function test_probundle_ohne_slides_enthaelt_nur_pro_datei(): void
|
|||
|
||||
$this->assertTrue($openResult === true);
|
||||
$this->assertSame(1, $zip->numFiles);
|
||||
$this->assertSame('sermon.pro', $zip->getNameIndex(0));
|
||||
$this->assertSame('data', $zip->getNameIndex(0));
|
||||
$zip->close();
|
||||
|
||||
@unlink($copiedPath);
|
||||
|
|
|
|||
Loading…
Reference in a new issue