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)) {
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->adapter 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)) {
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->adapter 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)) {
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))->formatListing($contents);
274    }
275
276    /**
277     * @inheritdoc
278     */
279    public function getMimetype($path)
280    {
281        $path = Util::normalizePath($path);
282        $this->assertPresent($path);
283
284        if ( ! $object = $this->getAdapter()->getMimetype($path)) {
285            return false;
286        }
287
288        return $object['mimetype'];
289    }
290
291    /**
292     * @inheritdoc
293     */
294    public function getTimestamp($path)
295    {
296        $path = Util::normalizePath($path);
297        $this->assertPresent($path);
298
299        if ( ! $object = $this->getAdapter()->getTimestamp($path)) {
300            return false;
301        }
302
303        return $object['timestamp'];
304    }
305
306    /**
307     * @inheritdoc
308     */
309    public function getVisibility($path)
310    {
311        $path = Util::normalizePath($path);
312        $this->assertPresent($path);
313
314        if (($object = $this->getAdapter()->getVisibility($path)) === false) {
315            return false;
316        }
317
318        return $object['visibility'];
319    }
320
321    /**
322     * @inheritdoc
323     */
324    public function getSize($path)
325    {
326        $path = Util::normalizePath($path);
327
328        if (($object = $this->getAdapter()->getSize($path)) === false || ! isset($object['size'])) {
329            return false;
330        }
331
332        return (int) $object['size'];
333    }
334
335    /**
336     * @inheritdoc
337     */
338    public function setVisibility($path, $visibility)
339    {
340        $path = Util::normalizePath($path);
341
342        return (bool) $this->getAdapter()->setVisibility($path, $visibility);
343    }
344
345    /**
346     * @inheritdoc
347     */
348    public function getMetadata($path)
349    {
350        $path = Util::normalizePath($path);
351        $this->assertPresent($path);
352
353        return $this->getAdapter()->getMetadata($path);
354    }
355
356    /**
357     * @inheritdoc
358     */
359    public function get($path, Handler $handler = null)
360    {
361        $path = Util::normalizePath($path);
362
363        if ( ! $handler) {
364            $metadata = $this->getMetadata($path);
365            $handler = $metadata['type'] === 'file' ? new File($this, $path) : new Directory($this, $path);
366        }
367
368        $handler->setPath($path);
369        $handler->setFilesystem($this);
370
371        return $handler;
372    }
373
374    /**
375     * Assert a file is present.
376     *
377     * @param string $path path to file
378     *
379     * @throws FileNotFoundException
380     *
381     * @return void
382     */
383    public function assertPresent($path)
384    {
385        if ($this->config->get('disable_asserts', false) === false && ! $this->has($path)) {
386            throw new FileNotFoundException($path);
387        }
388    }
389
390    /**
391     * Assert a file is absent.
392     *
393     * @param string $path path to file
394     *
395     * @throws FileExistsException
396     *
397     * @return void
398     */
399    public function assertAbsent($path)
400    {
401        if ($this->config->get('disable_asserts', false) === false && $this->has($path)) {
402            throw new FileExistsException($path);
403        }
404    }
405}
406