1<?php
2
3namespace League\Flysystem;
4
5use InvalidArgumentException;
6use League\Flysystem\Plugin\PluggableTrait;
7use League\Flysystem\Plugin\PluginNotFoundException;
8
9/**
10 * Class MountManager.
11 *
12 * Proxies methods to Filesystem (@see __call):
13 *
14 * @method AdapterInterface getAdapter($prefix)
15 * @method Config getConfig($prefix)
16 * @method array listFiles($directory = '', $recursive = false)
17 * @method array listPaths($directory = '', $recursive = false)
18 * @method array getWithMetadata($path, array $metadata)
19 * @method Filesystem flushCache()
20 * @method void assertPresent($path)
21 * @method void assertAbsent($path)
22 * @method Filesystem addPlugin(PluginInterface $plugin)
23 *
24 * @deprecated This functionality will be removed in 2.0
25 */
26class MountManager implements FilesystemInterface
27{
28    use PluggableTrait;
29
30    /**
31     * @var FilesystemInterface[]
32     */
33    protected $filesystems = [];
34
35    /**
36     * Constructor.
37     *
38     * @param FilesystemInterface[] $filesystems [:prefix => Filesystem,]
39     *
40     * @throws InvalidArgumentException
41     */
42    public function __construct(array $filesystems = [])
43    {
44        $this->mountFilesystems($filesystems);
45    }
46
47    /**
48     * Mount filesystems.
49     *
50     * @param FilesystemInterface[] $filesystems [:prefix => Filesystem,]
51     *
52     * @throws InvalidArgumentException
53     *
54     * @return $this
55     */
56    public function mountFilesystems(array $filesystems)
57    {
58        foreach ($filesystems as $prefix => $filesystem) {
59            $this->mountFilesystem($prefix, $filesystem);
60        }
61
62        return $this;
63    }
64
65    /**
66     * Mount filesystems.
67     *
68     * @param string              $prefix
69     * @param FilesystemInterface $filesystem
70     *
71     * @throws InvalidArgumentException
72     *
73     * @return $this
74     */
75    public function mountFilesystem($prefix, FilesystemInterface $filesystem)
76    {
77        if ( ! is_string($prefix)) {
78            throw new InvalidArgumentException(__METHOD__ . ' expects argument #1 to be a string.');
79        }
80
81        $this->filesystems[$prefix] = $filesystem;
82
83        return $this;
84    }
85
86    /**
87     * Get the filesystem with the corresponding prefix.
88     *
89     * @param string $prefix
90     *
91     * @throws FilesystemNotFoundException
92     *
93     * @return FilesystemInterface
94     */
95    public function getFilesystem($prefix)
96    {
97        if ( ! isset($this->filesystems[$prefix])) {
98            throw new FilesystemNotFoundException('No filesystem mounted with prefix ' . $prefix);
99        }
100
101        return $this->filesystems[$prefix];
102    }
103
104    /**
105     * Retrieve the prefix from an arguments array.
106     *
107     * @param array $arguments
108     *
109     * @throws InvalidArgumentException
110     *
111     * @return array [:prefix, :arguments]
112     */
113    public function filterPrefix(array $arguments)
114    {
115        if (empty($arguments)) {
116            throw new InvalidArgumentException('At least one argument needed');
117        }
118
119        $path = array_shift($arguments);
120
121        if ( ! is_string($path)) {
122            throw new InvalidArgumentException('First argument should be a string');
123        }
124
125        list($prefix, $path) = $this->getPrefixAndPath($path);
126        array_unshift($arguments, $path);
127
128        return [$prefix, $arguments];
129    }
130
131    /**
132     * @param string $directory
133     * @param bool   $recursive
134     *
135     * @throws InvalidArgumentException
136     * @throws FilesystemNotFoundException
137     *
138     * @return array
139     */
140    public function listContents($directory = '', $recursive = false)
141    {
142        list($prefix, $directory) = $this->getPrefixAndPath($directory);
143        $filesystem = $this->getFilesystem($prefix);
144        $result = $filesystem->listContents($directory, $recursive);
145
146        foreach ($result as &$file) {
147            $file['filesystem'] = $prefix;
148        }
149
150        return $result;
151    }
152
153    /**
154     * Call forwarder.
155     *
156     * @param string $method
157     * @param array  $arguments
158     *
159     * @throws InvalidArgumentException
160     * @throws FilesystemNotFoundException
161     *
162     * @return mixed
163     */
164    public function __call($method, $arguments)
165    {
166        list($prefix, $arguments) = $this->filterPrefix($arguments);
167
168        return $this->invokePluginOnFilesystem($method, $arguments, $prefix);
169    }
170
171    /**
172     * @param string $from
173     * @param string $to
174     * @param array  $config
175     *
176     * @throws InvalidArgumentException
177     * @throws FilesystemNotFoundException
178     * @throws FileExistsException
179     *
180     * @return bool
181     */
182    public function copy($from, $to, array $config = [])
183    {
184        list($prefixFrom, $from) = $this->getPrefixAndPath($from);
185
186        $buffer = $this->getFilesystem($prefixFrom)->readStream($from);
187
188        if ($buffer === false) {
189            return false;
190        }
191
192        list($prefixTo, $to) = $this->getPrefixAndPath($to);
193
194        $result = $this->getFilesystem($prefixTo)->writeStream($to, $buffer, $config);
195
196        if (is_resource($buffer)) {
197            fclose($buffer);
198        }
199
200        return $result;
201    }
202
203    /**
204     * List with plugin adapter.
205     *
206     * @param array  $keys
207     * @param string $directory
208     * @param bool   $recursive
209     *
210     * @throws InvalidArgumentException
211     * @throws FilesystemNotFoundException
212     *
213     * @return array
214     */
215    public function listWith(array $keys = [], $directory = '', $recursive = false)
216    {
217        list($prefix, $directory) = $this->getPrefixAndPath($directory);
218        $arguments = [$keys, $directory, $recursive];
219
220        return $this->invokePluginOnFilesystem('listWith', $arguments, $prefix);
221    }
222
223    /**
224     * Move a file.
225     *
226     * @param string $from
227     * @param string $to
228     * @param array  $config
229     *
230     * @throws InvalidArgumentException
231     * @throws FilesystemNotFoundException
232     *
233     * @return bool
234     */
235    public function move($from, $to, array $config = [])
236    {
237        list($prefixFrom, $pathFrom) = $this->getPrefixAndPath($from);
238        list($prefixTo, $pathTo) = $this->getPrefixAndPath($to);
239
240        if ($prefixFrom === $prefixTo) {
241            $filesystem = $this->getFilesystem($prefixFrom);
242            $renamed = $filesystem->rename($pathFrom, $pathTo);
243
244            if ($renamed && isset($config['visibility'])) {
245                return $filesystem->setVisibility($pathTo, $config['visibility']);
246            }
247
248            return $renamed;
249        }
250
251        $copied = $this->copy($from, $to, $config);
252
253        if ($copied) {
254            return $this->delete($from);
255        }
256
257        return false;
258    }
259
260    /**
261     * Invoke a plugin on a filesystem mounted on a given prefix.
262     *
263     * @param string $method
264     * @param array  $arguments
265     * @param string $prefix
266     *
267     * @throws FilesystemNotFoundException
268     *
269     * @return mixed
270     */
271    public function invokePluginOnFilesystem($method, $arguments, $prefix)
272    {
273        $filesystem = $this->getFilesystem($prefix);
274
275        try {
276            return $this->invokePlugin($method, $arguments, $filesystem);
277        } catch (PluginNotFoundException $e) {
278            // Let it pass, it's ok, don't panic.
279        }
280
281        $callback = [$filesystem, $method];
282
283        return call_user_func_array($callback, $arguments);
284    }
285
286    /**
287     * @param string $path
288     *
289     * @throws InvalidArgumentException
290     *
291     * @return string[] [:prefix, :path]
292     */
293    protected function getPrefixAndPath($path)
294    {
295        if (strpos($path, '://') < 1) {
296            throw new InvalidArgumentException('No prefix detected in path: ' . $path);
297        }
298
299        return explode('://', $path, 2);
300    }
301
302    /**
303     * Check whether a file exists.
304     *
305     * @param string $path
306     *
307     * @return bool
308     */
309    public function has($path)
310    {
311        list($prefix, $path) = $this->getPrefixAndPath($path);
312
313        return $this->getFilesystem($prefix)->has($path);
314    }
315
316    /**
317     * Read a file.
318     *
319     * @param string $path The path to the file.
320     *
321     * @throws FileNotFoundException
322     *
323     * @return string|false The file contents or false on failure.
324     */
325    public function read($path)
326    {
327        list($prefix, $path) = $this->getPrefixAndPath($path);
328
329        return $this->getFilesystem($prefix)->read($path);
330    }
331
332    /**
333     * Retrieves a read-stream for a path.
334     *
335     * @param string $path The path to the file.
336     *
337     * @throws FileNotFoundException
338     *
339     * @return resource|false The path resource or false on failure.
340     */
341    public function readStream($path)
342    {
343        list($prefix, $path) = $this->getPrefixAndPath($path);
344
345        return $this->getFilesystem($prefix)->readStream($path);
346    }
347
348    /**
349     * Get a file's metadata.
350     *
351     * @param string $path The path to the file.
352     *
353     * @throws FileNotFoundException
354     *
355     * @return array|false The file metadata or false on failure.
356     */
357    public function getMetadata($path)
358    {
359        list($prefix, $path) = $this->getPrefixAndPath($path);
360
361        return $this->getFilesystem($prefix)->getMetadata($path);
362    }
363
364    /**
365     * Get a file's size.
366     *
367     * @param string $path The path to the file.
368     *
369     * @throws FileNotFoundException
370     *
371     * @return int|false The file size or false on failure.
372     */
373    public function getSize($path)
374    {
375        list($prefix, $path) = $this->getPrefixAndPath($path);
376
377        return $this->getFilesystem($prefix)->getSize($path);
378    }
379
380    /**
381     * Get a file's mime-type.
382     *
383     * @param string $path The path to the file.
384     *
385     * @throws FileNotFoundException
386     *
387     * @return string|false The file mime-type or false on failure.
388     */
389    public function getMimetype($path)
390    {
391        list($prefix, $path) = $this->getPrefixAndPath($path);
392
393        return $this->getFilesystem($prefix)->getMimetype($path);
394    }
395
396    /**
397     * Get a file's timestamp.
398     *
399     * @param string $path The path to the file.
400     *
401     * @throws FileNotFoundException
402     *
403     * @return string|false The timestamp or false on failure.
404     */
405    public function getTimestamp($path)
406    {
407        list($prefix, $path) = $this->getPrefixAndPath($path);
408
409        return $this->getFilesystem($prefix)->getTimestamp($path);
410    }
411
412    /**
413     * Get a file's visibility.
414     *
415     * @param string $path The path to the file.
416     *
417     * @throws FileNotFoundException
418     *
419     * @return string|false The visibility (public|private) or false on failure.
420     */
421    public function getVisibility($path)
422    {
423        list($prefix, $path) = $this->getPrefixAndPath($path);
424
425        return $this->getFilesystem($prefix)->getVisibility($path);
426    }
427
428    /**
429     * Write a new file.
430     *
431     * @param string $path     The path of the new file.
432     * @param string $contents The file contents.
433     * @param array  $config   An optional configuration array.
434     *
435     * @throws FileExistsException
436     *
437     * @return bool True on success, false on failure.
438     */
439    public function write($path, $contents, array $config = [])
440    {
441        list($prefix, $path) = $this->getPrefixAndPath($path);
442
443        return $this->getFilesystem($prefix)->write($path, $contents, $config);
444    }
445
446    /**
447     * Write a new file using a stream.
448     *
449     * @param string   $path     The path of the new file.
450     * @param resource $resource The file handle.
451     * @param array    $config   An optional configuration array.
452     *
453     * @throws InvalidArgumentException If $resource is not a file handle.
454     * @throws FileExistsException
455     *
456     * @return bool True on success, false on failure.
457     */
458    public function writeStream($path, $resource, array $config = [])
459    {
460        list($prefix, $path) = $this->getPrefixAndPath($path);
461
462        return $this->getFilesystem($prefix)->writeStream($path, $resource, $config);
463    }
464
465    /**
466     * Update an existing file.
467     *
468     * @param string $path     The path of the existing file.
469     * @param string $contents The file contents.
470     * @param array  $config   An optional configuration array.
471     *
472     * @throws FileNotFoundException
473     *
474     * @return bool True on success, false on failure.
475     */
476    public function update($path, $contents, array $config = [])
477    {
478        list($prefix, $path) = $this->getPrefixAndPath($path);
479
480        return $this->getFilesystem($prefix)->update($path, $contents, $config);
481    }
482
483    /**
484     * Update an existing file using a stream.
485     *
486     * @param string   $path     The path of the existing file.
487     * @param resource $resource The file handle.
488     * @param array    $config   An optional configuration array.
489     *
490     * @throws InvalidArgumentException If $resource is not a file handle.
491     * @throws FileNotFoundException
492     *
493     * @return bool True on success, false on failure.
494     */
495    public function updateStream($path, $resource, array $config = [])
496    {
497        list($prefix, $path) = $this->getPrefixAndPath($path);
498
499        return $this->getFilesystem($prefix)->updateStream($path, $resource, $config);
500    }
501
502    /**
503     * Rename a file.
504     *
505     * @param string $path    Path to the existing file.
506     * @param string $newpath The new path of the file.
507     *
508     * @throws FileExistsException   Thrown if $newpath exists.
509     * @throws FileNotFoundException Thrown if $path does not exist.
510     *
511     * @return bool True on success, false on failure.
512     */
513    public function rename($path, $newpath)
514    {
515        list($prefix, $path) = $this->getPrefixAndPath($path);
516
517        return $this->getFilesystem($prefix)->rename($path, $newpath);
518    }
519
520    /**
521     * Delete a file.
522     *
523     * @param string $path
524     *
525     * @throws FileNotFoundException
526     *
527     * @return bool True on success, false on failure.
528     */
529    public function delete($path)
530    {
531        list($prefix, $path) = $this->getPrefixAndPath($path);
532
533        return $this->getFilesystem($prefix)->delete($path);
534    }
535
536    /**
537     * Delete a directory.
538     *
539     * @param string $dirname
540     *
541     * @throws RootViolationException Thrown if $dirname is empty.
542     *
543     * @return bool True on success, false on failure.
544     */
545    public function deleteDir($dirname)
546    {
547        list($prefix, $dirname) = $this->getPrefixAndPath($dirname);
548
549        return $this->getFilesystem($prefix)->deleteDir($dirname);
550    }
551
552    /**
553     * Create a directory.
554     *
555     * @param string $dirname The name of the new directory.
556     * @param array  $config  An optional configuration array.
557     *
558     * @return bool True on success, false on failure.
559     */
560    public function createDir($dirname, array $config = [])
561    {
562        list($prefix, $dirname) = $this->getPrefixAndPath($dirname);
563
564        return $this->getFilesystem($prefix)->createDir($dirname);
565    }
566
567    /**
568     * Set the visibility for a file.
569     *
570     * @param string $path       The path to the file.
571     * @param string $visibility One of 'public' or 'private'.
572     *
573     * @throws FileNotFoundException
574     *
575     * @return bool True on success, false on failure.
576     */
577    public function setVisibility($path, $visibility)
578    {
579        list($prefix, $path) = $this->getPrefixAndPath($path);
580
581        return $this->getFilesystem($prefix)->setVisibility($path, $visibility);
582    }
583
584    /**
585     * Create a file or update if exists.
586     *
587     * @param string $path     The path to the file.
588     * @param string $contents The file contents.
589     * @param array  $config   An optional configuration array.
590     *
591     * @return bool True on success, false on failure.
592     */
593    public function put($path, $contents, array $config = [])
594    {
595        list($prefix, $path) = $this->getPrefixAndPath($path);
596
597        return $this->getFilesystem($prefix)->put($path, $contents, $config);
598    }
599
600    /**
601     * Create a file or update if exists.
602     *
603     * @param string   $path     The path to the file.
604     * @param resource $resource The file handle.
605     * @param array    $config   An optional configuration array.
606     *
607     * @throws InvalidArgumentException Thrown if $resource is not a resource.
608     *
609     * @return bool True on success, false on failure.
610     */
611    public function putStream($path, $resource, array $config = [])
612    {
613        list($prefix, $path) = $this->getPrefixAndPath($path);
614
615        return $this->getFilesystem($prefix)->putStream($path, $resource, $config);
616    }
617
618    /**
619     * Read and delete a file.
620     *
621     * @param string $path The path to the file.
622     *
623     * @throws FileNotFoundException
624     *
625     * @return string|false The file contents, or false on failure.
626     */
627    public function readAndDelete($path)
628    {
629        list($prefix, $path) = $this->getPrefixAndPath($path);
630
631        return $this->getFilesystem($prefix)->readAndDelete($path);
632    }
633
634    /**
635     * Get a file/directory handler.
636     *
637     * @deprecated
638     *
639     * @param string  $path    The path to the file.
640     * @param Handler $handler An optional existing handler to populate.
641     *
642     * @return Handler Either a file or directory handler.
643     */
644    public function get($path, Handler $handler = null)
645    {
646        list($prefix, $path) = $this->getPrefixAndPath($path);
647
648        return $this->getFilesystem($prefix)->get($path);
649    }
650}
651