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