1<?php
2
3namespace League\Flysystem;
4
5use InvalidArgumentException;
6use League\Flysystem\Adapter\CanOverwriteFiles;
7use League\Flysystem\Plugin\PluggableTrait;
8use League\Flysystem\Util\ContentListingFormatter;
9
10/**
11 * @method array getWithMetadata(string $path, array $metadata)
12 * @method bool  forceCopy(string $path, string $newpath)
13 * @method bool  forceRename(string $path, string $newpath)
14 * @method array listFiles(string $path = '', boolean $recursive = false)
15 * @method array listPaths(string $path = '', boolean $recursive = false)
16 * @method array listWith(array $keys = [], $directory = '', $recursive = false)
17 */
18class Filesystem implements FilesystemInterface
19{
20    use PluggableTrait;
21    use ConfigAwareTrait;
22
23    /**
24     * @var AdapterInterface
25     */
26    protected $adapter;
27
28    /**
29     * Constructor.
30     *
31     * @param AdapterInterface $adapter
32     * @param Config|array     $config
33     */
34    public function __construct(AdapterInterface $adapter, $config = null)
35    {
36        $this->adapter = $adapter;
37        $this->setConfig($config);
38    }
39
40    /**
41     * Get the Adapter.
42     *
43     * @return AdapterInterface adapter
44     */
45    public function getAdapter()
46    {
47        return $this->adapter;
48    }
49
50    /**
51     * @inheritdoc
52     */
53    public function has($path)
54    {
55        $path = Util::normalizePath($path);
56
57        return strlen($path) === 0 ? false : (bool) $this->getAdapter()->has($path);
58    }
59
60    /**
61     * @inheritdoc
62     */
63    public function write($path, $contents, array $config = [])
64    {
65        $path = Util::normalizePath($path);
66        $this->assertAbsent($path);
67        $config = $this->prepareConfig($config);
68
69        return (bool) $this->getAdapter()->write($path, $contents, $config);
70    }
71
72    /**
73     * @inheritdoc
74     */
75    public function writeStream($path, $resource, array $config = [])
76    {
77        if ( ! is_resource($resource) || get_resource_type($resource) !== 'stream') {
78            throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.');
79        }
80
81        $path = Util::normalizePath($path);
82        $this->assertAbsent($path);
83        $config = $this->prepareConfig($config);
84
85        Util::rewindStream($resource);
86
87        return (bool) $this->getAdapter()->writeStream($path, $resource, $config);
88    }
89
90    /**
91     * @inheritdoc
92     */
93    public function put($path, $contents, array $config = [])
94    {
95        $path = Util::normalizePath($path);
96        $config = $this->prepareConfig($config);
97
98        if ( ! $this->getAdapter() instanceof CanOverwriteFiles && $this->has($path)) {
99            return (bool) $this->getAdapter()->update($path, $contents, $config);
100        }
101
102        return (bool) $this->getAdapter()->write($path, $contents, $config);
103    }
104
105    /**
106     * @inheritdoc
107     */
108    public function putStream($path, $resource, array $config = [])
109    {
110        if ( ! is_resource($resource) || get_resource_type($resource) !== 'stream') {
111            throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.');
112        }
113
114        $path = Util::normalizePath($path);
115        $config = $this->prepareConfig($config);
116        Util::rewindStream($resource);
117
118        if ( ! $this->getAdapter() instanceof CanOverwriteFiles && $this->has($path)) {
119            return (bool) $this->getAdapter()->updateStream($path, $resource, $config);
120        }
121
122        return (bool) $this->getAdapter()->writeStream($path, $resource, $config);
123    }
124
125    /**
126     * @inheritdoc
127     */
128    public function readAndDelete($path)
129    {
130        $path = Util::normalizePath($path);
131        $this->assertPresent($path);
132        $contents = $this->read($path);
133
134        if ($contents === false) {
135            return false;
136        }
137
138        $this->delete($path);
139
140        return $contents;
141    }
142
143    /**
144     * @inheritdoc
145     */
146    public function update($path, $contents, array $config = [])
147    {
148        $path = Util::normalizePath($path);
149        $config = $this->prepareConfig($config);
150
151        $this->assertPresent($path);
152
153        return (bool) $this->getAdapter()->update($path, $contents, $config);
154    }
155
156    /**
157     * @inheritdoc
158     */
159    public function updateStream($path, $resource, array $config = [])
160    {
161        if ( ! is_resource($resource) || get_resource_type($resource) !== 'stream') {
162            throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.');
163        }
164
165        $path = Util::normalizePath($path);
166        $config = $this->prepareConfig($config);
167        $this->assertPresent($path);
168        Util::rewindStream($resource);
169
170        return (bool) $this->getAdapter()->updateStream($path, $resource, $config);
171    }
172
173    /**
174     * @inheritdoc
175     */
176    public function read($path)
177    {
178        $path = Util::normalizePath($path);
179        $this->assertPresent($path);
180
181        if ( ! ($object = $this->getAdapter()->read($path))) {
182            return false;
183        }
184
185        return $object['contents'];
186    }
187
188    /**
189     * @inheritdoc
190     */
191    public function readStream($path)
192    {
193        $path = Util::normalizePath($path);
194        $this->assertPresent($path);
195
196        if ( ! $object = $this->getAdapter()->readStream($path)) {
197            return false;
198        }
199
200        return $object['stream'];
201    }
202
203    /**
204     * @inheritdoc
205     */
206    public function rename($path, $newpath)
207    {
208        $path = Util::normalizePath($path);
209        $newpath = Util::normalizePath($newpath);
210        $this->assertPresent($path);
211        $this->assertAbsent($newpath);
212
213        return (bool) $this->getAdapter()->rename($path, $newpath);
214    }
215
216    /**
217     * @inheritdoc
218     */
219    public function copy($path, $newpath)
220    {
221        $path = Util::normalizePath($path);
222        $newpath = Util::normalizePath($newpath);
223        $this->assertPresent($path);
224        $this->assertAbsent($newpath);
225
226        return $this->getAdapter()->copy($path, $newpath);
227    }
228
229    /**
230     * @inheritdoc
231     */
232    public function delete($path)
233    {
234        $path = Util::normalizePath($path);
235        $this->assertPresent($path);
236
237        return $this->getAdapter()->delete($path);
238    }
239
240    /**
241     * @inheritdoc
242     */
243    public function deleteDir($dirname)
244    {
245        $dirname = Util::normalizePath($dirname);
246
247        if ($dirname === '') {
248            throw new RootViolationException('Root directories can not be deleted.');
249        }
250
251        return (bool) $this->getAdapter()->deleteDir($dirname);
252    }
253
254    /**
255     * @inheritdoc
256     */
257    public function createDir($dirname, array $config = [])
258    {
259        $dirname = Util::normalizePath($dirname);
260        $config = $this->prepareConfig($config);
261
262        return (bool) $this->getAdapter()->createDir($dirname, $config);
263    }
264
265    /**
266     * @inheritdoc
267     */
268    public function listContents($directory = '', $recursive = false)
269    {
270        $directory = Util::normalizePath($directory);
271        $contents = $this->getAdapter()->listContents($directory, $recursive);
272
273        return (new ContentListingFormatter($directory, $recursive, $this->config->get('case_sensitive', true)))
274            ->formatListing($contents);
275    }
276
277    /**
278     * @inheritdoc
279     */
280    public function getMimetype($path)
281    {
282        $path = Util::normalizePath($path);
283        $this->assertPresent($path);
284
285        if (( ! $object = $this->getAdapter()->getMimetype($path)) || ! array_key_exists('mimetype', $object)) {
286            return false;
287        }
288
289        return $object['mimetype'];
290    }
291
292    /**
293     * @inheritdoc
294     */
295    public function getTimestamp($path)
296    {
297        $path = Util::normalizePath($path);
298        $this->assertPresent($path);
299
300        if (( ! $object = $this->getAdapter()->getTimestamp($path)) || ! array_key_exists('timestamp', $object)) {
301            return false;
302        }
303
304        return (int) $object['timestamp'];
305    }
306
307    /**
308     * @inheritdoc
309     */
310    public function getVisibility($path)
311    {
312        $path = Util::normalizePath($path);
313        $this->assertPresent($path);
314
315        if (( ! $object = $this->getAdapter()->getVisibility($path)) || ! array_key_exists('visibility', $object)) {
316            return false;
317        }
318
319        return $object['visibility'];
320    }
321
322    /**
323     * @inheritdoc
324     */
325    public function getSize($path)
326    {
327        $path = Util::normalizePath($path);
328        $this->assertPresent($path);
329
330        if (( ! $object = $this->getAdapter()->getSize($path)) || ! array_key_exists('size', $object)) {
331            return false;
332        }
333
334        return (int) $object['size'];
335    }
336
337    /**
338     * @inheritdoc
339     */
340    public function setVisibility($path, $visibility)
341    {
342        $path = Util::normalizePath($path);
343        $this->assertPresent($path);
344
345        return (bool) $this->getAdapter()->setVisibility($path, $visibility);
346    }
347
348    /**
349     * @inheritdoc
350     */
351    public function getMetadata($path)
352    {
353        $path = Util::normalizePath($path);
354        $this->assertPresent($path);
355
356        return $this->getAdapter()->getMetadata($path);
357    }
358
359    /**
360     * @inheritdoc
361     */
362    public function get($path, Handler $handler = null)
363    {
364        $path = Util::normalizePath($path);
365
366        if ( ! $handler) {
367            $metadata = $this->getMetadata($path);
368            $handler = ($metadata && $metadata['type'] === 'file') ? new File($this, $path) : new Directory($this, $path);
369        }
370
371        $handler->setPath($path);
372        $handler->setFilesystem($this);
373
374        return $handler;
375    }
376
377    /**
378     * Assert a file is present.
379     *
380     * @param string $path path to file
381     *
382     * @throws FileNotFoundException
383     *
384     * @return void
385     */
386    public function assertPresent($path)
387    {
388        if ($this->config->get('disable_asserts', false) === false && ! $this->has($path)) {
389            throw new FileNotFoundException($path);
390        }
391    }
392
393    /**
394     * Assert a file is absent.
395     *
396     * @param string $path path to file
397     *
398     * @throws FileExistsException
399     *
400     * @return void
401     */
402    public function assertAbsent($path)
403    {
404        if ($this->config->get('disable_asserts', false) === false && $this->has($path)) {
405            throw new FileExistsException($path);
406        }
407    }
408}
409