bucket = $bucket; return $this; } /** * Set the Amazon S3 client object that will send requests * * @param S3Client $client Amazon S3 client * * @return $this */ public function setClient(S3Client $client) { $this->client = $client; return $this; } /** * Set a custom iterator that returns \SplFileInfo objects for the source data * * @param \Iterator $iterator * * @return $this */ public function setSourceIterator(\Iterator $iterator) { $this->sourceIterator = $iterator; return $this; } /** * Set a custom object key provider instead of building one internally * * @param FileNameConverterInterface $converter Filename to object key provider * * @return $this */ public function setSourceFilenameConverter(FilenameConverterInterface $converter) { $this->sourceConverter = $converter; return $this; } /** * Set a custom object key provider instead of building one internally * * @param FileNameConverterInterface $converter Filename to object key provider * * @return $this */ public function setTargetFilenameConverter(FilenameConverterInterface $converter) { $this->targetConverter = $converter; return $this; } /** * Set the base directory of the files being transferred. The base directory is removed from each file path before * converting the file path to an object key or vice versa. * * @param string $baseDir Base directory, which will be deleted from each uploaded object key * * @return $this */ public function setBaseDir($baseDir) { $this->baseDir = $baseDir; return $this; } /** * Specify a prefix to prepend to each Amazon S3 object key or the prefix where object are stored in a bucket * * Can be used to upload files to a pseudo sub-folder key or only download files from a pseudo sub-folder * * @param string $keyPrefix Prefix for each uploaded key * * @return $this */ public function setKeyPrefix($keyPrefix) { // Removing leading slash $this->keyPrefix = ltrim($keyPrefix, '/'); return $this; } /** * Specify the delimiter used for the targeted filesystem (default delimiter is "/") * * @param string $delimiter Delimiter to use to separate paths * * @return $this */ public function setDelimiter($delimiter) { $this->delimiter = $delimiter; return $this; } /** * Specify an array of operation parameters to apply to each operation executed by the sync object * * @param array $params Associative array of PutObject (upload) GetObject (download) parameters * * @return $this */ public function setOperationParams(array $params) { $this->params = $params; return $this; } /** * Set the number of files that can be transferred concurrently * * @param int $concurrency Number of concurrent transfers * * @return $this */ public function setConcurrency($concurrency) { $this->concurrency = $concurrency; return $this; } /** * Set to true to force transfers even if a file already exists and has not changed * * @param bool $force Set to true to force transfers without checking if it has changed * * @return $this */ public function force($force = false) { $this->forcing = (bool) $force; return $this; } /** * Enable debug mode * * @param bool|resource $enabledOrResource Set to true or false to enable or disable debug output. Pass an opened * fopen resource to write to instead of writing to standard out. * @return $this */ public function enableDebugOutput($enabledOrResource = true) { $this->debug = $enabledOrResource; return $this; } /** * Add a filename filter that uses a regular expression to filter out files that you do not wish to transfer. * * @param string $search Regular expression search (in preg_match format). Any filename that matches this regex * will not be transferred. * @return $this */ public function addRegexFilter($search) { $this->assertFileIteratorSet(); $this->sourceIterator = new FilterIterator($this->sourceIterator, function ($i) use ($search) { return !preg_match($search, (string) $i); }); $this->sourceIterator->rewind(); return $this; } /** * Builds a UploadSync or DownloadSync object * * @return AbstractSync */ public function build() { $this->validateRequirements(); $this->sourceConverter = $this->sourceConverter ?: $this->getDefaultSourceConverter(); $this->targetConverter = $this->targetConverter ?: $this->getDefaultTargetConverter(); // Only wrap the source iterator in a changed files iterator if we are not forcing the transfers if (!$this->forcing) { $this->sourceIterator->rewind(); $this->sourceIterator = new ChangedFilesIterator( new \NoRewindIterator($this->sourceIterator), $this->getTargetIterator(), $this->sourceConverter, $this->targetConverter ); $this->sourceIterator->rewind(); } $sync = $this->specificBuild(); if ($this->params) { $this->addCustomParamListener($sync); } if ($this->debug) { $this->addDebugListener($sync, is_bool($this->debug) ? STDOUT : $this->debug); } return $sync; } /** * Hook to implement in subclasses * * @return AbstractSync */ abstract protected function specificBuild(); /** * @return \Iterator */ abstract protected function getTargetIterator(); /** * @return FilenameConverterInterface */ abstract protected function getDefaultSourceConverter(); /** * @return FilenameConverterInterface */ abstract protected function getDefaultTargetConverter(); /** * Add a listener to the sync object to output debug information while transferring * * @param AbstractSync $sync Sync object to listen to * @param resource $resource Where to write debug messages */ abstract protected function addDebugListener(AbstractSync $sync, $resource); /** * Validate that the builder has the minimal requirements * * @throws RuntimeException if the builder is not configured completely */ protected function validateRequirements() { if (!$this->client) { throw new RuntimeException('No client was provided'); } if (!$this->bucket) { throw new RuntimeException('No bucket was provided'); } $this->assertFileIteratorSet(); } /** * Ensure that the base file iterator has been provided * * @throws RuntimeException */ protected function assertFileIteratorSet() { // Interesting... Need to use isset because: Object of class GlobIterator could not be converted to boolean if (!isset($this->sourceIterator)) { throw new RuntimeException('A source file iterator must be specified'); } } /** * Wraps a generated iterator in a filter iterator that removes directories * * @param \Iterator $iterator Iterator to wrap * * @return \Iterator * @throws UnexpectedValueException */ protected function filterIterator(\Iterator $iterator) { $f = new FilterIterator($iterator, function ($i) { if (!$i instanceof \SplFileInfo) { throw new UnexpectedValueException('All iterators for UploadSync must return SplFileInfo objects'); } return $i->isFile(); }); $f->rewind(); return $f; } /** * Add the custom param listener to a transfer object * * @param HasDispatcherInterface $sync */ protected function addCustomParamListener(HasDispatcherInterface $sync) { $params = $this->params; $sync->getEventDispatcher()->addListener( UploadSync::BEFORE_TRANSFER, function (Event $e) use ($params) { if ($e['command'] instanceof CommandInterface) { $e['command']->overwriteWith($params); } } ); } /** * Create an Amazon S3 file iterator based on the given builder settings * * @return OpendirIterator */ protected function createS3Iterator() { // Ensure that the stream wrapper is registered $this->client->registerStreamWrapper(); // Calculate the opendir() bucket and optional key prefix location $dir = "s3://{$this->bucket}"; if ($this->keyPrefix) { $dir .= '/' . ltrim($this->keyPrefix, '/ '); } // Use opendir so that we can pass stream context to the iterator $dh = opendir($dir, stream_context_create(array( 's3' => array( 'delimiter' => '', 'listFilter' => function ($obj) { // Ensure that we do not try to download a glacier object. return !isset($obj['StorageClass']) || $obj['StorageClass'] != 'GLACIER'; } ) ))); // Add the trailing slash for the OpendirIterator concatenation if (!$this->keyPrefix) { $dir .= '/'; } return $this->filterIterator(new \NoRewindIterator(new OpendirIterator($dh, $dir))); } }