Rework album/tag downloads #19

Merged
Roflin merged 6 commits from tag-download into master 2020-03-11 20:04:31 +01:00

View File

@ -6,7 +6,7 @@
* Kabuki CMS (C) 2013-2019, Aaron van Geffen
*****************************************************************************/
class Download extends HTMLController
class Download
{
public function __construct()
{
@ -15,64 +15,47 @@ class Download extends HTMLController
if (!$user->isLoggedIn())
throw new NotAllowedException();
if(!isset($_GET['tag']))
throw new UnexpectedValueException('Must specify an album to download');
if (!isset($_GET['tag']))
throw new UserFacingException('No album or tag has been specified for download.');
$tag = (int)$_GET['tag'];
$album = Tag::fromId($tag);
if($album->kind !== 'Album')
throw new UnexpectedValueException('Specified tag does not correspond to an album');
if (isset($_SESSION['current_export']))
Review

Ik zie deze check, maar ik zie niet waar deze session variable geset word? Of mis ik iets.

Ik zie deze check, maar ik zie niet waar deze session variable geset word? Of mis ik iets.
Review

Oef, terecht. Ik vermoed dat ik die ben verloren in een overijverige rebase. Gaan we fixen.

Oef, terecht. Ik vermoed dat ik die ben verloren in een overijverige rebase. Gaan we fixen.
throw new UserFacingException('You can only export one album at the same time. Please wait until the other download finishes, or try again later.');
//Yes TOCTOU but it does not need to be perfect.
$lock_file = join('/', [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();
// So far so good?
$this->exportAlbum($album);
exit;
}
private function exportAlbum($album)
private function exportAlbum(Tag $album)
{
$files = [];
$album_ids = array_merge([$album->id_tag], $this->getChildAlbumIds($album->id_tag));
foreach($album_ids as $album_id)
foreach ($album_ids as $album_id)
{
$iterator = AssetIterator::getByOptions(
[
'id_tag' => $album_id
]
);
while($asset = $iterator->Next())
{
$files[] = join(DIRECTORY_SEPARATOR, ['.', $asset->getSubdir(), $asset->getFilename()]);
}
$iterator = AssetIterator::getByOptions(['id_tag' => $album_id]);
while ($asset = $iterator->next())
$files[] = join(DIRECTORY_SEPARATOR, [$asset->getSubdir(), $asset->getFilename()]);
}
$descriptorspec = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
0 => ['pipe', 'r'], // STDIN
1 => ['pipe', 'w'], // STDOUT
];
$command = 'tar --null -cf - -T -';
// Prevent simultaneous exports.
$_SESSION['current_export'] = $album->id_tag;
// Allow new exports if the connection is terminated unexpectedly (e.g. when a user aborts a download).
register_shutdown_function(function() {
if (isset($_SESSION['current_export']))
unset($_SESSION['current_export']);
});
$command = 'tar -cf - -C ' . escapeshellarg(ASSETSDIR) . ' --null -T -';
$proc = proc_open($command, $descriptorspec, $pipes, ASSETSDIR);
@ -85,36 +68,53 @@ class Download extends HTMLController
if(!$pipes[1])
throw new UnexpectedValueException('Could not open pipe for STDOUT');
$album_name = $album->tag;
// STDOUT should not block.
stream_set_blocking($pipes[1], 0);
header('Pragma: no-cache');
header('Content-Description: File Download');
header('Content-disposition: attachment; filename="' . $album_name . '.tar"');
header('Content-disposition: attachment; filename="' . $album->tag . '.tar"');
header('Content-Type: application/octet-stream');
header('Content-Transfer-Encoding: binary');
foreach($files as $file)
// Write filenames to include to STDIN, separated by null bytes.
foreach ($files as $file)
fwrite($pipes[0], $file . "\0");
// Close STDIN pipe to start archiving.
fclose($pipes[0]);
while($chunk = stream_get_contents($pipes[1], 4096))
echo $chunk;
// At this point, end output buffering so we can enjoy more than ~62MB of photos.
ob_end_flush();
do
{
// Read STDOUT as `tar` is doing its work.
echo stream_get_contents($pipes[1], 4096);
// Are we still running?
$status = proc_get_status($proc);
}
while (!empty($status) && $status['running']);
// Close STDOUT pipe and clean up process.
fclose($pipes[1]);
proc_close($proc);
// Allow new exports from this point onward.
unset($_SESSION['current_export']);
}
private function getChildAlbumIds($parent_id)
{
$ids = [];
$albums = Tag::getAlbums($parent_id, 0, PHP_INT_MAX, 'object');
foreach($albums as $album)
$albums = Tag::getAlbums($parent_id, 0, PHP_INT_MAX);
foreach ($albums as $album)
{
$ids[] = $album->id_tag;
$ids = array_merge($ids, $this->getChildAlbumIds($album->id_tag));
$ids[] = $album['id_tag'];
$ids = array_merge($ids, $this->getChildAlbumIds($album['id_tag']));
}
return $ids;