Zipování souborů a jejich následné stažení

Webster.K
Člen | 212
+
0
-

Zdravím všechny, dávám dohromady script, který by měl podle parametru stáhnout určitá data z databáze (objednávky). mám tento kód:

private function zipArchiv() {
		//načtení hodnot z DB
        $objednavky = $this->database->table('objednavky')->where('docasna', 0);

		//vytvoření PDFek a jejich uložení na server přes knihovnu mpdf
        foreach ($objednavky as $objednavka) {
            $this->exportPDF($objednavka->id_objednavky);
        }

		//načtení obsahu adresáře
        $dir = './files/pdf/';
        foreach (Finder::findFiles('*.pdf')->in($dir) as $key => $file) {
            //echo $key; // $key je řetězec s názvem souboru včetně cesty
            //echo $file; // $file je objektem SplFileInfo
            $files[] = $file;
        }

		//komprese
        $zipname = 'archiv_s_objednavkama.zip';
        $zip = new \ZipArchive;
        $zip->open($zipname, \ZipArchive::CREATE);
        foreach ($files as $soubor) {
            $zip->addFile($soubor);
        }
        $zip->close();

		///uložení zazipovaného souboru
        header('Content-Type: application/zip');
        header('Content-disposition: attachment; filename=' . $zipname);
        header('Content-Length: ' . filesize($zipname));
        readfile($zipname);
    }

Vše se zdá být v pořádku. Objednávky se z DB načtou, přes exportPDF (mpdf knihovna) to do složky na serveru uloží soubory které budou potřeba pro archivování, pak se otevře adresář, do kterého se vše přidá, soubor se vytvoří a uloží. Vše na první pohled vypadá, že je ok. Problém je, že ten vytvořený soubor obsahuje nejdříve adresář „.“, potom adresář files a potom adresář pdf a v něm ty samotné soubory. To by samo o sobě nebyl problém. Problém nastane, když chci jednotlivé soubory otevřít, v tu chvíli to hodí chybu archívu. Soubor má nejspíše správnou velikost, nicméně stažení souboru z archívu se už nepovede, někdo nějaký nápad?

Webster.K
Člen | 212
+
0
-

Tak už jsem na to přišel a funguje to v pohodě :)

private function zipArchiv() {
        \Nette\Utils\FileSystem::delete('./files/pdf/');
        \Nette\Utils\FileSystem::createDir('./files/pdf/');
        $objednavky = $this->database->table('objednavky')->where('docasna', 1);

        foreach ($objednavky as $objednavka) {
            $this->exportPDF($objednavka->id_objednavky);
        }

        $dir = './files/pdf/';
        foreach (Finder::findFiles('*.pdf')->in($dir) as $key => $file) {
            //echo $key; // $key je řetězec s názvem souboru včetně cesty
            //echo $file; // $file je objektem SplFileInfo
            $files[] = ['path'=>$key,'file'=>\Nette\Utils\FileSystem::read($file),'name'=>basename($key)];


        }

        $zipname = 'archiv_s_objednavkama.zip';
        $zip = new \ZipArchive;
        $zip->open($dir . $zipname, \ZipArchive::CREATE);
        foreach ($files as $soubor) {
            $zip->addFile($soubor['path'],$soubor['name']);
            //$zip->addFromString($soubor['path'],$soubor['file']);
        }
        $zip->close();

        header('Content-Type: application/zip');
        header('Content-disposition: attachment; filename=' . $zipname);
        header('Content-Length: ' . filesize($dir . $zipname));
        readfile($dir . $zipname);
    }
Webster.K
Člen | 212
+
0
-

Tak jsem to trochu testoval a při velkém množství souboru je to poměrně hodně pomalé :/ dlouho se čeká, než se soubor vytvoří. Jde nějakým způsobem udělat, jako je to třeba na google drive, když se připravuje velký soubor, tak něco podobného? Zjistil jsem, že při extrémních souborech to hodí 504 Bad Gateway – timeout

Phalanx
Člen | 310
+
0
-

Dělám v podstatě to samé co ty. Řesím to jen nastavením time limitu na 10 minut. set_time_limit(10*60);

Webster.K
Člen | 212
+
0
-

Time limit jsem sice nastavil, a zatím to funguje, ale do budoucna mi to nepřijde úplně jako šťastné řešení :/ přeci jen mít nějaký loading bar, kde bude vidět postup generování souboru a pak jen odkaz na jeho stažení by mi přišel šikovnější :)

dms
Člen | 94
+
+2
-

Ahoj, na jednom projektu pouzivame maennchen/zipstream-php Funguje to tak ze se zip generuje a zaroven streamuje, tzn zacne se hned stahovat a uzivatel neceka nez probehne nejaka dlouha akce, ale vidi ze se mu soubor stahuje hned. Generujeme to pro galerie, kde je stovky fotek a x giga objemy dat takze generovani zipu trvalo dlouho, ale tohle to vyresilo. Pouziti knihovny je stejne jako u ziparchivu, ve finale jeste jednodussi. Jediny problem je, ze se dopredu nevi celkova velikost souboru, takze uzivatel stahuje soubor, ale nema prehled o tom kdy to dobehne.

Webster.K
Člen | 212
+
0
-

To že by uživatel nevěděl, kdy to doběhne mi nevadí, potřebuji hlavně, aby to vyvolalo akci na uložení souboru, to že to bude pak něco dělat je mi už jedno. Jsem to zkusil použít ale bez úspěchu :/ chová se mi to úplně stejně jako to řešení, co jsem měl před tím. Mám tenhle kód:

public function renderZip() {
        $this->zipArchiv();
    }

    private function zipArchiv() {
        \Nette\Utils\FileSystem::delete('./files/pdf/');
        \Nette\Utils\FileSystem::createDir('./files/pdf/');
        $objednavky = $this->database->table('objednavky')->where('docasna', 1);

        foreach ($objednavky as $objednavka) {
            $this->exportPDF($objednavka->id_objednavky);
        }

        $dir = './files/pdf/';
        foreach (Finder::findFiles('*.pdf')->in($dir) as $key => $file) {
            $files[] = ['path' => $key, 'file' => \Nette\Utils\FileSystem::read($file), 'name' => basename($key)];
        }

        $zipname = 'archiv_s_objednavkama.zip';
        $zip = new \ZipStream\ZipStream($zipname);
        foreach ($files as $soubor) {
            $zip->addFileFromPath($soubor['name'],$soubor['path']);
        }
        $zip->finish();
    }
Webster.K
Člen | 212
+
0
-

Tak už jsem zjistil, kde je problém… to generování PDF trvá celkem dlouho a dokud nejsou dogenerovaný, tak se stahování nespustí :/

dms
Člen | 94
+
0
-

Vytvor ten zipstream pred generovanim pdf objednavek a pridavej ty pdf do streamu uz rovnou ve foreach $objednavky. Takhle je to jasne. Nejdriv se ti pdf generuji a pak zipstreamujes v dalsim foreachi. Vse by melo byt v jednom foreach a pak to snad bude fungovat

Webster.K
Člen | 212
+
0
-

tak jsem to opravil na toto

private function zipArchiv() {
        \Nette\Utils\FileSystem::delete('./files/pdf/');
        \Nette\Utils\FileSystem::createDir('./files/pdf/');
        $objednavky = $this->database->table('objednavky')->where('docasna', 1);

        $zipname = 'archiv_s_objednavkama.zip';
        $zip = new \ZipStream\ZipStream($zipname);
        $dir = './files/pdf/';

        foreach ($objednavky as $objednavka) {
            $this->exportPDF($objednavka->id_objednavky);
            foreach (Finder::findFiles($objednavka->cislo_zakazky . '.pdf')->in($dir) as $key) {
                $file = ['path' => $key, 'name' => basename($key)];
                break;
            }
            $zip->addFileFromPath($file['name'], $file['path']);
        }
        $zip->finish();
    }

a funguje to v pořádku :) děkuji všem za rady. Spolu s tím jsem ale přišel ještě na jednu nepříjemnou věc a to je, že po dobu stahování ten konkrétní uživatel, který si soubor stahuje nemůže zobrazit nic jiného, pokračování práce na webu tedy může pokračovat až ve chvíli, kdy se soubor dostáhne

Editoval Webster.K (6. 11. 2018 7:35)

MajklNajt
Člen | 502
+
+1
-

Webster.K napsal(a):
a funguje to v pořádku :) děkuji všem za rady. Spolu s tím jsem ale přišel ještě na jednu nepříjemnou věc a to je, že po dobu stahování ten konkrétní uživatel, který si soubor stahuje nemůže zobrazit nic jiného, pokračování práce na webu tedy může pokračovat až ve chvíli, kdy se soubor dostáhne

to bude kvôli zámku sessions, skús pred tým odosielaním použiť session_write_close() resp. čistejšie Nette\Http\Session::close()

Webster.K
Člen | 212
+
0
-

Nebude to mít vliv na to, že tohle stažení lze provést jen u přihlášeného uživatele? Aby to pak nezpůsobovalo nestandardní chování aplikace, protože jinak než se přihlásit se do aplikace dostat nejde

CZechBoY
Člen | 3608
+
+1
-

No ty si autorizuješ uživatele a potom zavřeš session (nebudeš už moct zapisovat, část by mělo jít pořád) a potom stáhneš/pošleš soubor.

MajklNajt
Člen | 502
+
+1
-

funkcia session_write_close() ti zatvorí session pre zápis, čiže už nebudeš môcť robiť zmenu dát v session (čo v momente posielania zipu už asi nepotrebuješ, prihláseného užívateľa testuješ na začiatku životného cyklu presenteru príp. v modeli)

Webster.K
Člen | 212
+
0
-

Aha, vlastně jo no :D takže tam to už nevadí. Ok, to jsem provedl, hodil jsem tam po začátku vytvoření zipu tedy \Nette\http\Session::close(); a hodilo to chybu:
Deprecated
Non-static method Nette\Http\Session::close() should not be called statically, assuming $this from incompatible context

a

$sess = new \Nette\Http\Session;
        $sess->close();

hodí: Argument 1 passed to Nette\Http\Session::__construct() must implement interface Nette\Http\IRequest, none given, called in /var/www/html/pavel_web/app/presenters/ObjednavkyPresenter.php on line 239 and defined

Editoval Webster.K (6. 11. 2018 9:19)

MajklNajt
Člen | 502
+
+1
-

Webster.K napsal(a):

Aha, vlastně jo no :D takže tam to už nevadí. Ok, to jsem provedl, hodil jsem tam po začátku vytvoření zipu tedy \Nette\http\Session::close(); a hodilo to chybu:
Deprecated
Non-static method Nette\Http\Session::close() should not be called statically, assuming $this from incompatible context

Nette\http\Session si musíš vyžiadať z DI (cez constructor alebo @inject) potom volať $this->session->close()

Editoval MajklNajt (6. 11. 2018 9:19)

Webster.K
Člen | 212
+
0
-

Ajo, už mi to došlo, už to funguje tak jak má :) jsem si měl ráno udělat kafe dřív, než začnu programovat :D díky :)

japlavaren
Člen | 404
+
0
-

Webster.K napsal(a):

tak jsem to opravil na toto

private function zipArchiv() {
        \Nette\Utils\FileSystem::delete('./files/pdf/');
        \Nette\Utils\FileSystem::createDir('./files/pdf/');
        $objednavky = $this->database->table('objednavky')->where('docasna', 1);

        $zipname = 'archiv_s_objednavkama.zip';
        $zip = new \ZipStream\ZipStream($zipname);
        $dir = './files/pdf/';

        foreach ($objednavky as $objednavka) {
            $this->exportPDF($objednavka->id_objednavky);
            foreach (Finder::findFiles($objednavka->cislo_zakazky . '.pdf')->in($dir) as $key) {
                $file = ['path' => $key, 'name' => basename($key)];
                break;
            }
            $zip->addFileFromPath($file['name'], $file['path']);
        }
        $zip->finish();
    }

a funguje to v pořádku :) děkuji všem za rady. Spolu s tím jsem ale přišel ještě na jednu nepříjemnou věc a to je, že po dobu stahování ten konkrétní uživatel, který si soubor stahuje nemůže zobrazit nic jiného, pokračování práce na webu tedy může pokračovat až ve chvíli, kdy se soubor dostáhne

tento riadok mi prijde zbytocny – naco to prehladavas ked vies ze to vrati iba jeden subor?

<?php
foreach (Finder::findFiles($objednavka->cislo_zakazky . '.pdf')->in($dir) as $key) {
	$file = ['path' => $key, 'name' => basename($key)];
	break;
}
?>