forked from Public/pics
Merge pull request 'Rework album/tag downloads' (#19) from tag-download into master
This commit is contained in:
commit
909d50efa8
@ -6,7 +6,7 @@
|
|||||||
* Kabuki CMS (C) 2013-2019, Aaron van Geffen
|
* Kabuki CMS (C) 2013-2019, Aaron van Geffen
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
class Download extends HTMLController
|
class Download
|
||||||
{
|
{
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@ -15,64 +15,47 @@ class Download extends HTMLController
|
|||||||
if (!$user->isLoggedIn())
|
if (!$user->isLoggedIn())
|
||||||
throw new NotAllowedException();
|
throw new NotAllowedException();
|
||||||
|
|
||||||
if(!isset($_GET['tag']))
|
if (!isset($_GET['tag']))
|
||||||
throw new UnexpectedValueException('Must specify an album to download');
|
throw new UserFacingException('No album or tag has been specified for download.');
|
||||||
|
|
||||||
$tag = (int)$_GET['tag'];
|
$tag = (int)$_GET['tag'];
|
||||||
$album = Tag::fromId($tag);
|
$album = Tag::fromId($tag);
|
||||||
|
|
||||||
if($album->kind !== 'Album')
|
if (isset($_SESSION['current_export']))
|
||||||
throw new UnexpectedValueException('Specified tag does not correspond to an album');
|
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');
|
|
||||||
|
|
||||||
|
// So far so good?
|
||||||
$this->exportAlbum($album);
|
$this->exportAlbum($album);
|
||||||
}
|
exit;
|
||||||
finally
|
|
||||||
{
|
|
||||||
fclose($fp);
|
|
||||||
unlink($lock_file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw new UnexpectedValueException('Another export is busy, please try again later');
|
|
||||||
|
|
||||||
exit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function exportAlbum($album)
|
private function exportAlbum(Tag $album)
|
||||||
{
|
{
|
||||||
$files = [];
|
$files = [];
|
||||||
|
|
||||||
$album_ids = array_merge([$album->id_tag], $this->getChildAlbumIds($album->id_tag));
|
$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(
|
$iterator = AssetIterator::getByOptions(['id_tag' => $album_id]);
|
||||||
[
|
while ($asset = $iterator->next())
|
||||||
'id_tag' => $album_id
|
$files[] = join(DIRECTORY_SEPARATOR, [$asset->getSubdir(), $asset->getFilename()]);
|
||||||
]
|
|
||||||
);
|
|
||||||
while($asset = $iterator->Next())
|
|
||||||
{
|
|
||||||
$files[] = join(DIRECTORY_SEPARATOR, ['.', $asset->getSubdir(), $asset->getFilename()]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$descriptorspec = [
|
$descriptorspec = [
|
||||||
0 => ['pipe', 'r'],
|
0 => ['pipe', 'r'], // STDIN
|
||||||
1 => ['pipe', 'w'],
|
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);
|
$proc = proc_open($command, $descriptorspec, $pipes, ASSETSDIR);
|
||||||
|
|
||||||
@ -85,36 +68,53 @@ class Download extends HTMLController
|
|||||||
if(!$pipes[1])
|
if(!$pipes[1])
|
||||||
throw new UnexpectedValueException('Could not open pipe for STDOUT');
|
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('Pragma: no-cache');
|
||||||
header('Content-Description: File Download');
|
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-Type: application/octet-stream');
|
||||||
header('Content-Transfer-Encoding: binary');
|
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");
|
fwrite($pipes[0], $file . "\0");
|
||||||
|
|
||||||
|
// Close STDIN pipe to start archiving.
|
||||||
fclose($pipes[0]);
|
fclose($pipes[0]);
|
||||||
|
|
||||||
while($chunk = stream_get_contents($pipes[1], 4096))
|
// At this point, end output buffering so we can enjoy more than ~62MB of photos.
|
||||||
echo $chunk;
|
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]);
|
fclose($pipes[1]);
|
||||||
|
|
||||||
proc_close($proc);
|
proc_close($proc);
|
||||||
|
|
||||||
|
// Allow new exports from this point onward.
|
||||||
|
unset($_SESSION['current_export']);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getChildAlbumIds($parent_id)
|
private function getChildAlbumIds($parent_id)
|
||||||
{
|
{
|
||||||
$ids = [];
|
$ids = [];
|
||||||
|
|
||||||
$albums = Tag::getAlbums($parent_id, 0, PHP_INT_MAX, 'object');
|
$albums = Tag::getAlbums($parent_id, 0, PHP_INT_MAX);
|
||||||
foreach($albums as $album)
|
foreach ($albums as $album)
|
||||||
{
|
{
|
||||||
$ids[] = $album->id_tag;
|
$ids[] = $album['id_tag'];
|
||||||
$ids = array_merge($ids, $this->getChildAlbumIds($album->id_tag));
|
$ids = array_merge($ids, $this->getChildAlbumIds($album['id_tag']));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $ids;
|
return $ids;
|
||||||
|
Loading…
Reference in New Issue
Block a user