pics/controllers/Download.php
Dennis Brentjes 9d10d53d9f Fixes album export by reducing memory usage of the scripts.
Chunks the output buffer to 4kB chunks. This means the Download.php
script will no longer run out of memory when trying to stream a
tar file the output buffer.
2020-02-26 21:41:38 +01:00

120 lines
2.8 KiB
PHP

<?php
/*****************************************************************************
* Download.php
* Contains the code to download an album.
*
* Kabuki CMS (C) 2013-2019, Aaron van Geffen
*****************************************************************************/
class Download extends HTMLController
{
public function __construct()
{
// Ensure we're logged in at this point.
$user = Registry::get('user');
if (!$user->isLoggedIn())
throw new NotAllowedException();
if(!isset($_GET['tag']))
throw new UnexpectedValueException('Must specify an album to download');
$tag = (int)$_GET['tag'];
$album = Tag::fromId($tag);
if($album->kind !== 'Album')
throw new UnexpectedValueException('Specified tag does not correspond to an album');
//Yes TOCTOU but it does not need to be perfect.
$lock_file = join('/', array(sys_get_temp_dir(), 'pics-export.lock'));
if(!file_exists($lock_file))
{
try
{
$fp = fopen($lock_file, 'x');
if(!$fp)
throw new UnexpectedValueException('Could not open lock-file');
$this->exportAlbum($album);
}
finally
{
fclose($fp);
unlink($lock_file);
}
}
else
throw new UnexpectedValueException('Another export is busy, please try again later');
exit();
}
private function exportAlbum($album)
{
$files = array();
$descriptorspec = array(
0 => array('pipe', 'r'),
1 => array('pipe', 'w'),
);
$command = 'tar --null -cf - -T -';
$proc = proc_open($command, $descriptorspec, $pipes, ASSETSDIR);
if(!$proc)
throw new UnexpectedValueException('Could not execute TAR command');
if(!$pipes[0])
throw new UnexpectedValueException('Could not open pipe for STDIN');
if(!$pipes[1])
throw new UnexpectedValueException('Could not open pipe for STDOUT');
$album_name = $album->tag;
header('Pragma: no-cache');
header('Content-Description: File Download');
header('Content-disposition: attachment; filename="' . $album_name . '.tar"');
header('Content-Type: application/octet-stream');
header('Content-Transfer-Encoding: binary');
$album_ids = array_merge(array($album->id_tag), $this->getChildAlbumIds($album->id_tag));
foreach($album_ids as $album_id)
{
$iterator = AssetIterator::getByOptions(
array(
'id_tag' => $album_id
)
);
while($asset = $iterator->Next())
{
fwrite($pipes[0], join(DIRECTORY_SEPARATOR, array('.', $asset->getSubdir(), $asset->getFilename())) . "\0");
}
}
fclose($pipes[0]);
while($chunk = stream_get_contents($pipes[1], 4096))
echo $chunk;
fclose($pipes[1]);
proc_close($proc);
}
private function getChildAlbumIds($parent_id)
{
$ids = array();
$albums = Tag::getAlbums($parent_id, 0, PHP_INT_MAX, 'object');
foreach($albums as $album)
{
$ids[] = $album->id_tag;
$ids = array_merge($ids, $this->getChildAlbumIds($album->id_tag));
}
return $ids;
}
}