Skip to content

Commit

Permalink
Refactor unzipFile method in class.core.php for improved progress…
Browse files Browse the repository at this point in the history
… handling and add copyright information in `quickpick.js`

uses 131mb of ram during processing
  • Loading branch information
N6REJ committed Aug 27, 2024
1 parent 3b52dcb commit 6637678
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 80 deletions.
184 changes: 110 additions & 74 deletions core/classes/class.core.php
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ public function __toString()
return 'core object';
}

/**
/**
* Unzips a file to the specified directory and provides progress updates.
*
* This method uses the 7-Zip command-line tool to extract the contents of a zip file.
Expand All @@ -472,88 +472,124 @@ public function __toString()
* - int $currentFile: The current file number being extracted.
* - int $totalFiles: The total number of files to be extracted.
*
* @return array An array containing the result of the extraction:
* - 'success' => true and 'numFiles' => int on success.
* - 'error' => string and 'numFiles' => int on failure.
* @global object $bearsamppRoot Global object to get core paths.
*
* @return array|false An array containing the result of the extraction on success or failure:
* - On success: ['success' => true, 'numFiles' => int]
* - On failure: ['error' => string, 'numFiles' => int]
* - Returns false if the 7-Zip executable is not found.
*/
public function unzipFile($filePath, $destination, $progressCallback = null)
{
global $bearsamppRoot;

$sevenZipPath = $this->getLibsPath() . '/7zip/7za.exe';

if (!file_exists($sevenZipPath)) {
Util::logError('7za.exe not found at: ' . $sevenZipPath);
return false;
}

// Command to test the archive and get the number of files
$testCommand = escapeshellarg($sevenZipPath) . ' t ' . escapeshellarg($filePath) . ' -y -bsp1';
$testOutput = shell_exec($testCommand);

// Extract the number of files from the test command output
preg_match('/Files: (\d+)/', $testOutput, $matches);
$numFiles = isset($matches[1]) ? (int)$matches[1] : 0;
Util::logTrace('Number of files to be extracted: ' . $numFiles);

// Command to extract the archive
$command = escapeshellarg($sevenZipPath) . ' x ' . escapeshellarg($filePath) . ' -y -bsp1 -bb0 -o' . escapeshellarg($destination);
Util::logTrace('Executing command: ' . $command);

$process = popen($command, 'rb');

if ($process) {
$buffer = '';
while (!feof($process)) {
$buffer .= fread($process, 8192); // Read in chunks of 8KB
while (($pos = strpos($buffer, "\r")) !== false) {
$line = substr($buffer, 0, $pos);
$buffer = substr($buffer, $pos + 1);
$line = trim($line); // Remove any leading/trailing whitespace
Util::logTrace("Processing line: $line");

// Adjusted preg_match pattern to match the percentage preceded by a space or at the start of the line
if ($progressCallback && preg_match('/(?:^|\s)(\d+)%/', $line, $matches)) {
$currentPercentage = intval($matches[1]);
Util::logTrace("Extraction progress: $currentPercentage%");
call_user_func($progressCallback, $currentPercentage);
Util::logTrace("Progress callback called with percentage: $currentPercentage");
} else {
Util::logTrace("Line did not match pattern: $line");
public function unzipFile($filePath, $destination, $progressCallback = null)
{
global $bearsamppRoot;

$sevenZipPath = $this->getLibsPath() . '/7zip/7za.exe';

if ( !file_exists( $sevenZipPath ) ) {
Util::logError( '7za.exe not found at: ' . $sevenZipPath );

return false;
}

// Command to test the archive and get the number of files
$testCommand = escapeshellarg( $sevenZipPath ) . ' t ' . escapeshellarg( $filePath ) . ' -y -bsp1';
$testOutput = shell_exec( $testCommand );

// Extract the number of files from the test command output
preg_match( '/Files: (\d+)/', $testOutput, $matches );
$numFiles = isset( $matches[1] ) ? (int) $matches[1] : 0;
Util::logTrace( 'Number of files to be extracted: ' . $numFiles );

// Command to extract the archive
$command = escapeshellarg( $sevenZipPath ) . ' x ' . escapeshellarg( $filePath ) . ' -y -bsp1 -bb0 -o' . escapeshellarg( $destination );
Util::logTrace( 'Executing command: ' . $command );

$process = popen( $command, 'rb' );

if ( $process ) {
$buffer = '';
while ( !feof( $process ) ) {
$buffer .= fread( $process, 8192 ); // Read in chunks of 8KB
while ( ($pos = strpos( $buffer, "\r" )) !== false ) {
$line = substr( $buffer, 0, $pos );
$buffer = substr( $buffer, $pos + 1 );
$line = trim( $line ); // Remove any leading/trailing whitespace
Util::logTrace( "Processing line: $line" );

// Check if the line indicates everything is okay
if ( $line === "Everything is Ok" ) {
if ( $progressCallback ) {
Util::logTrace( "Extraction progress: 100%" );
call_user_func( $progressCallback, 100 );
Util::logTrace( "Progress callback called with percentage: 100" );
}
}
else if ( $progressCallback && preg_match( '/(?:^|\s)(\d+)%/', $line, $matches ) ) {
$currentPercentage = intval( $matches[1] );
Util::logTrace( "Extraction progress: $currentPercentage%" );
call_user_func( $progressCallback, $currentPercentage );
Util::logTrace( "Progress callback called with percentage: $currentPercentage" );
}
else {
Util::logTrace( "Line did not match pattern: $line" );
}
}
}

// Process any remaining data in the buffer
if ( !empty( $buffer ) ) {
$line = trim( $buffer );
Util::logTrace( "Processing remaining line: $line" );

// Check if the remaining line indicates everything is okay
if ( $line === "Everything is Ok" ) {
if ( $progressCallback ) {
Util::logTrace( "Extraction progress: 100%" );
call_user_func( $progressCallback, 100 );
Util::logTrace( "Progress callback called with percentage: 100" );
}
}
else if ( $progressCallback && preg_match( '/(?:^|\s)(\d+)%/', $line, $matches ) ) {
$currentPercentage = intval( $matches[1] );
Util::logTrace( "Extraction progress: $currentPercentage%" );
call_user_func( $progressCallback, $currentPercentage );
Util::logTrace( "Progress callback called with percentage: $currentPercentage" );
}
else {
Util::logTrace( "Remaining line did not match pattern: $line" );
}
}
}

// Process any remaining data in the buffer
if (!empty($buffer)) {
$line = trim($buffer);
Util::logTrace("Processing remaining line: $line");

if ($progressCallback && preg_match('/(?:^|\s)(\d+)%/', $line, $matches)) {
$currentPercentage = intval($matches[1]);
Util::logTrace("Extraction progress: $currentPercentage%");
call_user_func($progressCallback, $currentPercentage);
Util::logTrace("Progress callback called with percentage: $currentPercentage");
} else {
Util::logTrace("Remaining line did not match pattern: $line");
$returnVar = pclose( $process );
Util::logTrace( 'Command return value: ' . $returnVar );

// Set progress to 100% if the command was successful
if ( $returnVar === 0 && $progressCallback ) {
Util::logTrace( "Extraction completed successfully. Setting progress to 100%" );
call_user_func( $progressCallback, 100 );
Util::logTrace( "Progress callback called with percentage: 100" );

// Adding a small delay to ensure the progress bar update is processed
usleep( 100000 ); // 100 milliseconds
}
}

$returnVar = pclose($process);
Util::logTrace('Command return value: ' . $returnVar);
if ( $returnVar === 0 ) {
Util::logDebug( 'Successfully unzipped file to: ' . $destination );

if ($returnVar === 0) {
Util::logDebug('Successfully unzipped file to: ' . $destination);
return ['success' => true, 'numFiles' => $numFiles];
} else {
Util::logError('Failed to unzip file. Command return value: ' . $returnVar);
return ['error' => 'Failed to unzip file', 'numFiles' => $numFiles];
return ['success' => true, 'numFiles' => $numFiles];
}
else {
Util::logError( 'Failed to unzip file. Command return value: ' . $returnVar );

return ['error' => 'Failed to unzip file', 'numFiles' => $numFiles];
}
}
else {
Util::logError( 'Failed to open process for command: ' . $command );

return ['error' => 'Failed to open process', 'numFiles' => $numFiles];
}
} else {
Util::logError('Failed to open process for command: ' . $command);
return ['error' => 'Failed to open process', 'numFiles' => $numFiles];
}
}

/**
* Fetches a file from a given URL and saves it to a specified file path.
Expand Down
11 changes: 5 additions & 6 deletions core/resources/homepage/js/quickpick.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ function hideall() {
*
* @param {string} moduleName - The name of the module to install.
* @param {string} version - The version of the module to install.
* @returns {Promise<void>} - A promise that resolves when the installation is complete.
*/
async function installModule(moduleName, version) {
const url = AJAX_URL;
Expand Down Expand Up @@ -182,19 +183,15 @@ async function installModule(moduleName, version) {
console.log('Progress:', data.progress);
const progressValue = data.progress;
progressbar.style.width = '100%';
//progressbar.setAttribute('aria-valuenow', progressValue);
if (isDownloading) {
progressbar.innerText = `${progressValue} KBytes Downloaded`;
} else {
progressbar.innerText = `${progressValue} Extracted`;
}
} else if (data.success) {

console.log(data);
isCompleted = true;
messageData = data.message;
// window.alert(data.message);

} else if (data.error) {
console.error('Error:', data.error);
window.alert(`Error: ${data.error}`);
Expand All @@ -205,13 +202,15 @@ async function installModule(moduleName, version) {
// Ignore JSON parse errors for incomplete parts
}
}

// Clear responseText to keep only the unprocessed part
responseText = parts[parts.length - 1].startsWith('{') ? parts[parts.length - 1] : '';
}
} catch (error) {
console.error('Failed to install module:', error);
window.alert('Failed to install module: ' + error.message);
} finally {
if (isCompleted === true)
{
if (isCompleted === true) {
confirm(messageData);
}
setTimeout(() => {
Expand Down

0 comments on commit 6637678

Please sign in to comment.