1<?php
2declare(strict_types=1);
3
4namespace ILIAS\Filesystem\Provider\FlySystem;
5
6use ILIAS\Filesystem\Exception\FileAlreadyExistsException;
7use ILIAS\Filesystem\Exception\FileNotFoundException;
8use ILIAS\Filesystem\Exception\IOException;
9use ILIAS\Filesystem\Provider\FileStreamAccess;
10use ILIAS\Filesystem\Stream\FileStream;
11use ILIAS\Filesystem\Stream\Streams;
12use League\Flysystem\FileExistsException;
13use League\Flysystem\FilesystemInterface;
14
15/**
16 * Class FlySystemFileStreamAccess
17 *
18 * Streaming access implementation of the fly system library.
19 *
20 * @author  Nicolas Schäfli <ns@studer-raimann.ch>
21 * @since 5.3
22 * @version 1.0.0
23 */
24final class FlySystemFileStreamAccess implements FileStreamAccess
25{
26
27    /**
28     * @var FilesystemInterface $flySystemFS
29     */
30    private $flySystemFS;
31
32    /**
33     * FlySystemFileStreamAccess constructor.
34     *
35     * @param FilesystemInterface $flySystemFS   A configured fly system filesystem instance.
36     */
37    public function __construct(FilesystemInterface $flySystemFS)
38    {
39        $this->flySystemFS = $flySystemFS;
40    }
41
42    /**
43     * Opens a readable stream of the file.
44     * Please make sure to close the stream after the work is done with Stream::close()
45     *
46     * @param string $path The path to the file which should be used to open the new stream.
47     *
48     * @return FileStream The newly created file stream.
49     *
50     * @throws FileNotFoundException    If the file could not be found.
51     * @throws IOException              If the stream could not be opened.
52     *
53     * @since   5.3
54     * @version 1.0
55     *
56     * @see FileStream::close()
57     */
58    public function readStream(string $path) : FileStream
59    {
60        try {
61            $resource = $this->flySystemFS->readStream($path);
62            if ($resource === false) {
63                throw new IOException("Could not open stream for file \"$path\"");
64            }
65
66            $stream = Streams::ofResource($resource);
67            return $stream;
68        } catch (\League\Flysystem\FileNotFoundException $ex) {
69            throw new FileNotFoundException("File \"$path\" not found.", 0, $ex);
70        }
71    }
72
73
74    /**
75     * Writes the stream to a new file.
76     * The directory path to the file will be created.
77     *
78     * The stream will be closed after the write operation is done. Please note that the
79     * resource must be detached from the stream in order to write to the file.
80     *
81     * @param string                    $path   The file which should be used to write the stream into.
82     * @param FileStream                $stream The stream which should be written into the new file.
83     *
84     * @return void
85     * @throws FileAlreadyExistsException If the file already exists.
86     * @throws IOException If the file could not be written to the filesystem.
87     * @since   5.3
88     * @version 1.0
89     *
90     * @see     FileStream::detach()
91     */
92    public function writeStream(string $path, FileStream $stream)
93    {
94        $resource = $stream->detach();
95        try {
96            if (!is_resource($resource)) {
97                throw new \InvalidArgumentException('The given stream must not be detached.');
98            }
99
100            $result = $this->flySystemFS->writeStream($path, $resource);
101
102            if ($result === false) {
103                throw new IOException("Could not write stream to file \"$path\"");
104            }
105        } catch (FileExistsException $ex) {
106            throw new FileAlreadyExistsException("File \"$path\" already exists.", 0, $ex);
107        } finally {
108            if (is_resource($resource)) {
109                fclose($resource);
110            }
111        }
112    }
113
114
115    /**
116     * Creates a new file or updates an existing one.
117     * If the file is updated its content will be truncated before writing the stream.
118     *
119     * The stream will be closed after the write operation is done. Please note that the
120     * resource must be detached from the stream in order to write to the file.
121     *
122     * @param string                     $path   The file which should be used to write the stream into.
123     * @param FileStream                 $stream The stream which should be written to the file.
124     *
125     * @return void
126     * @throws IOException If the stream could not be written to the file.
127     * @since   5.3
128     * @version 1.0
129     *
130     * @see     FileStream::detach()
131     */
132    public function putStream(string $path, FileStream $stream)
133    {
134        $resource = $stream->detach();
135        try {
136            if (!is_resource($resource)) {
137                throw new \InvalidArgumentException('The given stream must not be detached.');
138            }
139
140            $result = $this->flySystemFS->putStream($path, $resource);
141
142            if ($result === false) {
143                throw new IOException("Could not put stream content into \"$path\"");
144            }
145        } finally {
146            if (is_resource($resource)) {
147                fclose($resource);
148            }
149        }
150    }
151
152
153    /**
154     * Updates an existing file.
155     * The file content will be truncated to 0.
156     *
157     * The stream will be closed after the write operation is done. Please note that the
158     * resource must be detached from the stream in order to write to the file.
159     *
160     * @param string                    $path   The path to the file which should be updated.
161     * @param FileStream                $stream The stream which should be used to update the file content.
162     *
163     * @return void
164     * @throws FileNotFoundException If the file which should be updated doesn't exist.
165     * @throws IOException If the file could not be updated.
166     * @since   5.3
167     * @version 1.0
168     */
169    public function updateStream(string $path, FileStream $stream)
170    {
171        $resource = $stream->detach();
172        try {
173            if (!is_resource($resource)) {
174                throw new \InvalidArgumentException('The given stream must not be detached.');
175            }
176
177            $result = $this->flySystemFS->updateStream($path, $resource);
178
179            if ($result === false) {
180                throw new IOException("Could not update file \"$path\"");
181            }
182        } catch (\League\Flysystem\FileNotFoundException $ex) {
183            throw new FileNotFoundException("File \"$path\" not found.", 0, $ex);
184        } finally {
185            if (is_resource($resource)) {
186                fclose($resource);
187            }
188        }
189    }
190}
191