loadMissing('territorio.zona'); if ($motivo = $mappa->motivoNonPubblicabile()) { throw new RuntimeException($motivo); } $this->pulisciVecchie(); $token = Str::random(40); $this->scrivi($token, ['stato' => self::IN_CORSO, 'mappa_id' => $mappa->id]); PreparaAnteprimaPiantina::dispatch($token); return $token; } /** Lo stato dell'anteprima, o null se il token non esiste o è scaduto. */ public function stato(string $token): ?array { return Cache::get($this->chiave($token)); } /** Il lavoro vero, eseguito dal job. */ public function prepara(string $token): void { $stato = $this->stato($token); if (! $stato || $stato['stato'] !== self::IN_CORSO) { return; } try { $mappa = TerritorioMappa::with('territorio.zona')->find($stato['mappa_id']); if (! $mappa) { throw new RuntimeException('La piantina non esiste più.'); } if ($motivo = $mappa->motivoNonPubblicabile()) { throw new RuntimeException($motivo); } // L'impronta si prende adesso, sullo stesso stato da cui escono // immagine e PDF: è quello che la conferma dovrà ritrovare. $impronta = $mappa->impronta(); [$immagine, $estensione] = $this->renderer->immagine($mappa->geometria()); $disco = Storage::disk('local'); $percorsoImmagine = self::CARTELLA . "/{$token}.{$estensione}"; $percorsoPdf = self::CARTELLA . "/{$token}.pdf"; if (! $disco->put($percorsoImmagine, $immagine)) { throw new RuntimeException('Non è stato possibile salvare l\'immagine dell\'anteprima.'); } $binario = $this->pdf->genera($mappa->territorio, $mappa, $disco->path($percorsoImmagine)); if (! $disco->put($percorsoPdf, $binario)) { throw new RuntimeException('Non è stato possibile salvare il PDF dell\'anteprima.'); } $this->scrivi($token, $stato + [ 'impronta' => $impronta, 'immagine' => $percorsoImmagine, 'estensione' => $estensione, 'pdf' => $percorsoPdf, ], self::PRONTA); } catch (\Throwable $e) { report($e); $this->segnaErrore($token, $e->getMessage()); } } public function segnaErrore(string $token, string $messaggio): void { $stato = $this->stato($token); if (! $stato) { return; } $this->scrivi($token, $stato + ['messaggio' => $messaggio], self::ERRORE); } /** Percorso assoluto del PDF di un'anteprima pronta, o null. */ public function pdfAnteprima(string $token): ?string { $stato = $this->stato($token); if (($stato['stato'] ?? null) !== self::PRONTA || ! Storage::disk('local')->exists($stato['pdf'])) { return null; } return Storage::disk('local')->path($stato['pdf']); } /** * Rende ufficiale l'anteprima: PDF del territorio sostituito (il * precedente va nello storico), immagine della piantina aggiornata, bozza * segnata come pubblicata. */ public function conferma(string $token): TerritorioMappa { $stato = $this->stato($token); if (($stato['stato'] ?? null) !== self::PRONTA) { throw new RuntimeException('L\'anteprima non è più disponibile: generala di nuovo.'); } $mappa = TerritorioMappa::with('territorio.zona')->find($stato['mappa_id']); if (! $mappa || ! $mappa->collegata()) { throw new RuntimeException('La piantina non è più collegata a un territorio: non si può pubblicare.'); } if ($mappa->impronta() !== $stato['impronta']) { throw new RuntimeException('La piantina o i dati del territorio sono cambiati dopo l\'anteprima: rigenerala per pubblicare la versione attuale.'); } $disco = Storage::disk('local'); if (! $disco->exists($stato['pdf']) || ! $disco->exists($stato['immagine'])) { throw new RuntimeException('I file dell\'anteprima non ci sono più: generala di nuovo.'); } $this->pdf->sostituisciConPdf($mappa->territorio, $disco->get($stato['pdf']), 'piantine'); $this->renderer->archivia($mappa, $disco->get($stato['immagine']), $stato['estensione']); $mappa->update([ 'geojson_pubblicato' => $mappa->geojson, 'impronta_pubblicata' => $stato['impronta'], 'pubblicata_at' => now(), ]); activity() ->causedBy(auth()->user()) ->performedOn($mappa->territorio) ->log('piantina_pubblicata'); $this->annulla($token); return $mappa->fresh('territorio'); } /** Butta l'anteprima: file e stato. */ public function annulla(string $token): void { $stato = $this->stato($token); if ($stato) { Storage::disk('local')->delete(array_filter([$stato['immagine'] ?? null, $stato['pdf'] ?? null])); } Cache::forget($this->chiave($token)); } private function scrivi(string $token, array $dati, ?string $nuovoStato = null): void { if ($nuovoStato !== null) { $dati['stato'] = $nuovoStato; } Cache::put($this->chiave($token), $dati, self::DURATA_SECONDI); } private function chiave(string $token): string { return 'piantine:anteprima:' . $token; } /** * Le anteprime aperte e mai confermate né annullate (finestra chiusa) * lascerebbero file per sempre: chi ne avvia una nuova pulisce quelle * scadute. */ private function pulisciVecchie(): void { $disco = Storage::disk('local'); $limite = now()->subSeconds(self::DURATA_SECONDI)->getTimestamp(); foreach ($disco->files(self::CARTELLA) as $file) { if ($disco->lastModified($file) < $limite) { $disco->delete($file); } } } }