Last active
June 22, 2024 08:38
-
-
Save theking2/3c5d8f7accf72e2b08349e9084da84d0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php declare(strict_types=1); | |
/** | |
* ZipStepResponse | |
* | |
* This class creates a ZIP archive of the specified directory. | |
* With the name of the archive, it creates a new archive name if the current one is too large. | |
* in small steps to avoid PHP timeouts | |
*/ | |
class ZipStepResponse | |
{ | |
private int $archiveCount; | |
private int $refreshRate = 10; | |
private int $maxFileSizeMB = 250; | |
private int $batchSize = 10; | |
private array $files; | |
private int $fileCount; | |
private int $fileIndex; | |
private int $step; | |
private ?string $message = null; | |
public function __construct( | |
readonly string $directory, | |
private string $archiveName) | |
{ | |
session_start(); | |
if( !array_key_exists( 'files', $_SESSION ) ) { | |
$_SESSION['archiveName'] = $archiveName; | |
$_SESSION['archiveCount'] = 0; | |
$_SESSION['files'] = $this->getFilesRecursively( $directory ); | |
} | |
// Read the request | |
$this->archiveName = $_SESSION['archiveName']; | |
$this->archiveCount = $_SESSION['archiveCount']; | |
$this->files = $_SESSION['files']; | |
$request = json_decode( file_get_contents( 'php://input' ) ); | |
$this->fileIndex = $request->fileIndex; | |
$this->registerFatal(); | |
} | |
public function setBatchSize( int $batchSize ): self | |
{ | |
$this->batchSize = $batchSize; | |
return $this; | |
} | |
public function setRefreshRate( int $refreshRate ): self | |
{ | |
$this->refreshRate = $refreshRate; | |
return $this; | |
} | |
public function setMaxFileSize( int $maxFileSizeMB ): self | |
{ | |
$this->maxFileSizeMB = $maxFileSizeMB; | |
return $this; | |
} | |
public function processStep(): array | |
{ | |
$basepathLength = strlen( $this->directory ); | |
$fileCount = count( $this->files ); | |
$secondsElapsed = 0; | |
try { | |
// (Re)open the ZIP file | |
for( | |
$fileIndex = $this->fileIndex, $start = hrtime( true ); | |
$fileIndex < $fileCount and $secondsElapsed < $this->refreshRate; | |
) { | |
$zip = $this->openArchive(); | |
// add a batch of #batchSize files | |
for( $i = 0; $fileIndex < $fileCount and $i < $this->batchSize; $i++, $fileIndex++ ) { | |
$currentfile = $this->files[ $fileIndex ]; | |
if( file_exists( $currentfile ) ) { | |
$zip->addFile( filepath: $currentfile, entryname: str_replace('\\', '/', substr( $currentfile, $basepathLength )) ); | |
} | |
} | |
// Close wil add the batch of (#batchSize) files. | |
$zip->close(); | |
$secondsElapsed = ( hrtime( true ) - $start ) / 1e+9; | |
} | |
if( $fileIndex === $fileCount ) { | |
$fileIndex = null; // Done | |
session_unset(); | |
session_destroy(); | |
} | |
} catch ( Exception $e ) { | |
$message = $e->getMessage(); | |
session_unset(); | |
session_destroy(); | |
} | |
return [ | |
'fileIndex' => $fileIndex, | |
'total' => count( $this->files ), | |
'secondsElapsed' => number_format( $secondsElapsed, 1 ), | |
'message' => $message ?? null | |
]; | |
} | |
/** | |
* currentAcrhiveName | |
* create a new archive name if the current one | |
*/ | |
private function currentAcrhiveName(): string | |
{ | |
$archiveName = $this->archiveName . '-' . $this->archiveCount . '.zip'; | |
if( file_exists( $archiveName ) and ( (filesize( $archiveName )>>6) > $this->maxFileSizeMB ) ) { | |
// Current archive is too large, create a new one | |
$this->archiveCount++; | |
$_SESSION['archiveCount'] = $this->archiveCount; | |
$archiveName = $this->currentAcrhiveName(); | |
} | |
return $archiveName; | |
} | |
/** | |
* openArchive | |
*/ | |
private function openArchive(): ZipArchive | |
{ | |
$archiveName = $this->currentAcrhiveName(); | |
$zip = new ZipArchive(); | |
$result = $zip->open( $archiveName, ZipArchive::CREATE ); | |
if( $result !== TRUE ) { | |
throw new Exception( "Unable to open {$archiveName}\n" ); | |
} | |
return $zip; | |
} | |
/** | |
* getFilesRecursively | |
* create a list of files in a directory, recursively | |
* @param $directory | |
*/ | |
private function getFilesRecursively( string $directory ): array | |
{ | |
$files = []; | |
try { | |
// Create a RecursiveDirectoryIterator | |
$dirIterator = new RecursiveDirectoryIterator( $directory ); | |
// Create a RecursiveIteratorIterator | |
$iterator = new RecursiveIteratorIterator( $dirIterator ); | |
// Loop through the iterator | |
foreach( $iterator as $fileInfo ) { | |
// Skip directories (you can remove this condition if you want to include directories) | |
if( $fileInfo->isDir() ) | |
continue; | |
if( $fileInfo->getBasename() === basename( __FILE__ ) ) | |
continue; // skip self | |
if( $fileInfo->getExtension() === 'zip' ) | |
continue; // skip ZIP files | |
// Get the full path of the file | |
$files[] = $fileInfo->getPathname(); | |
} | |
} catch ( Exception $e ) { | |
echo "Error: " . $e->getMessage(); | |
} | |
return $files; | |
} | |
private function registerFatal(): void | |
{ | |
register_shutdown_function( function() { | |
$error = error_get_last(); | |
if( $error !== null ) { | |
echo json_encode( [ 'error' => $error ] ); | |
} | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Call with
and a js
with this html