forked from Public/pics
		
	Adds the ability to export an album using streaming tar.
This commit is contained in:
		
							parent
							
								
									768f5ee529
								
							
						
					
					
						commit
						20db3561cf
					
				
							
								
								
									
										122
									
								
								controllers/Download.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								controllers/Download.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,122 @@
 | 
				
			|||||||
 | 
					<?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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							$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())
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									$files[] = join(DIRECTORY_SEPARATOR, array('.', $asset->getSubdir(), $asset->getFilename()));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							$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');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							foreach($files as $file)
 | 
				
			||||||
 | 
								fwrite($pipes[0], $file . "\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;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -61,7 +61,13 @@ class ViewPhotoAlbum extends HTMLController
 | 
				
			|||||||
		// Can we do fancy things here?
 | 
							// Can we do fancy things here?
 | 
				
			||||||
		// !!! TODO: permission system?
 | 
							// !!! TODO: permission system?
 | 
				
			||||||
		$buttons = [];
 | 
							$buttons = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (Registry::get('user')->isLoggedIn())
 | 
							if (Registry::get('user')->isLoggedIn())
 | 
				
			||||||
 | 
								$buttons[] = [
 | 
				
			||||||
 | 
									'url' => BASEURL . '/download/?tag=' . $id_tag,
 | 
				
			||||||
 | 
									'caption' => 'Download this album',
 | 
				
			||||||
 | 
								];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			$buttons[] = [
 | 
								$buttons[] = [
 | 
				
			||||||
				'url' => BASEURL . '/uploadmedia/?tag=' . $id_tag,
 | 
									'url' => BASEURL . '/uploadmedia/?tag=' . $id_tag,
 | 
				
			||||||
				'caption' => 'Upload new photos here',
 | 
									'caption' => 'Upload new photos here',
 | 
				
			||||||
 | 
				
			|||||||
@ -28,6 +28,7 @@ class Dispatcher
 | 
				
			|||||||
			'suggest' => 'ProvideAutoSuggest',
 | 
								'suggest' => 'ProvideAutoSuggest',
 | 
				
			||||||
			'timeline' => 'ViewTimeline',
 | 
								'timeline' => 'ViewTimeline',
 | 
				
			||||||
			'uploadmedia' => 'UploadMedia',
 | 
								'uploadmedia' => 'UploadMedia',
 | 
				
			||||||
 | 
								'download' => 'Download',
 | 
				
			||||||
		];
 | 
							];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Work around PHP's FPM not always providing PATH_INFO.
 | 
							// Work around PHP's FPM not always providing PATH_INFO.
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user