Test Failed
Pull Request — master (#24)
by Filippo
12:43
created

ChunksFilesystem::listChunks()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 11
nc 1
nop 1
dl 0
loc 15
rs 9.9
c 1
b 0
f 0
1
<?php
2
3
namespace Jobtech\LaravelChunky\Support;
4
5
use Illuminate\Support\Arr;
6
use Illuminate\Support\Collection;
7
use Illuminate\Support\Str;
8
use Jobtech\LaravelChunky\Chunk;
9
use Jobtech\LaravelChunky\Events\ChunkAdded;
10
use Jobtech\LaravelChunky\Events\ChunkDeleted;
11
use Jobtech\LaravelChunky\Exceptions\ChunkyException;
12
use Keven\AppendStream\AppendStream;
13
use Keven\Flysystem\Concatenate\Concatenate;
14
use Neutron\TemporaryFilesystem\Manager;
15
use Symfony\Component\HttpFoundation\File\File;
16
17
class ChunksFilesystem extends FileSystem
18
{
19
    /** @var Manager|null */
20
    private static $manager;
21
22
    /** @var array */
23
    private static $collections = [];
24
25
    private function temporaryFilesystem(): Manager
26
    {
27
        if (static::$manager === null) {
0 ignored issues
show
Bug introduced by
Since $manager is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $manager to at least protected.
Loading history...
28
            static::$manager = Manager::create();
29
        }
30
31
        return static::$manager;
32
    }
33
34
    private function addTemporaryContext(string $folder)
35
    {
36
        array_push(static::$collections, $folder);
0 ignored issues
show
Bug introduced by
Since $collections is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $collections to at least protected.
Loading history...
37
    }
38
39
    /** {@inheritdoc} */
40
    public function disk($disk = null): ?string
41
    {
42
        if (! empty($disk) && is_string($disk)) {
43
            $this->disk = $disk;
44
        }
45
46
        return $this->disk;
47
    }
48
49
    /** {@inheritdoc} */
50
    public function folder($folder = null): ?string
51
    {
52
        if (! empty($folder) && is_string($folder)) {
53
            $this->folder = $folder;
54
        }
55
56
        return $this->folder;
57
    }
58
59
    /**
60
     * @param string $folder
61
     *
62
     * @return string
63
     */
64
    public function fullPath(string $folder): string
65
    {
66
        $suffix = Str::endsWith($folder, DIRECTORY_SEPARATOR) ? '' : DIRECTORY_SEPARATOR;
67
68
        if (Str::startsWith($folder, $this->folder)) {
69
            return $folder.$suffix;
70
        } else if(!Str::endsWith($this->folder, $folder.$suffix)) {
71
            return $this->folder.$folder.$suffix;
72
        }
73
74
        return $this->folder;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->folder could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
75
    }
76
77
    /**
78
     * Retrieve every chunks' folder.
79
     *
80
     * @return array
81
     */
82
    public function folders(): array
83
    {
84
        return $this->filesystem()
85
            ->disk($this->disk)
86
            ->directories($this->folder);
87
    }
88
89
    /**
90
     * @param string|null $folder
91
     *
92
     * @return array
93
     */
94
    public function list($folder = null): array
95
    {
96
        $folder = $this->fullPath($folder);
0 ignored issues
show
Bug introduced by
It seems like $folder can also be of type null; however, parameter $folder of Jobtech\LaravelChunky\Su...sFilesystem::fullPath() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

96
        $folder = $this->fullPath(/** @scrutinizer ignore-type */ $folder);
Loading history...
97
98
        return $this->filesystem()
99
            ->disk($this->disk)
100
            ->files($folder);
101
    }
102
103
    /**
104
     * @param \Jobtech\LaravelChunky\Chunk $chunk
105
     * @param string $folder
106
     * @param array $options
107
     *
108
     * @return Chunk
109
     */
110
    public function store(Chunk $chunk, string $folder, $options = []): Chunk
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

110
    public function store(Chunk $chunk, string $folder, /** @scrutinizer ignore-unused */ $options = []): Chunk

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
111
    {
112
        if (! $chunk->getOriginalPath() instanceof File) {
113
            throw new ChunkyException('Path must be a file');
114
        }
115
116
        $file = fopen($chunk->getPath(), 'r');
117
        $destination = $this->fullPath($folder).$chunk->getSlug();
118
        $path = $this->filesystem()->disk($this->disk)->put(
119
            $destination, $file
0 ignored issues
show
Bug introduced by
It seems like $file can also be of type false; however, parameter $contents of Illuminate\Filesystem\FilesystemAdapter::put() does only seem to accept resource|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

119
            $destination, /** @scrutinizer ignore-type */ $file
Loading history...
120
        );
121
122
        fclose($file);
0 ignored issues
show
Bug introduced by
It seems like $file can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

122
        fclose(/** @scrutinizer ignore-type */ $file);
Loading history...
123
124
        $chunk->setPath($path);
125
126
        event(new ChunkAdded($chunk));
127
128
        return $chunk;
129
    }
130
131
    /**
132
     * Delete all chunks and, once empty, delete the folder.
133
     *
134
     * @param string $folder
135
     *
136
     * @return bool
137
     */
138
    public function delete(string $folder): bool
139
    {
140
        // Check temporary files
141
        if (in_array($folder, static::$collections)) {
0 ignored issues
show
Bug introduced by
Since $collections is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $collections to at least protected.
Loading history...
142
            $this->temporaryFilesystem()->clean($folder);
143
        }
144
145
        $folder = $this->fullPath($folder);
146
147
        if (! $this->filesystem()->disk($this->disk)->exists($folder)) {
148
            return true;
149
        }
150
151
        foreach ($this->listChunks($folder) as $chunk) {
152
            $this->deleteChunk($chunk);
153
        }
154
155
        return $this->filesystem()->disk($this->disk)
156
            ->deleteDirectory($folder);
157
    }
158
159
    /**
160
     * Delete all chunks and, once empty, delete the folder.
161
     *
162
     * @param string $folder
163
     *
164
     * @return bool
165
     */
166
    public function deleteChunk(Chunk $chunk): bool
167
    {
168
        if (! $this->filesystem()->disk($this->disk)->exists($chunk->getPath())) {
169
            return true;
170
        }
171
172
        $deleted = $this->filesystem()
173
            ->disk($this->disk)
174
            ->delete($chunk->getPath());
175
176
        if ($deleted) {
177
            event(new ChunkDeleted($chunk));
178
        }
179
180
        return $deleted;
181
    }
182
183
    /**
184
     * @param string $folder
185
     * @return \Illuminate\Support\Collection
186
     */
187
    public function listChunks(string $folder)
188
    {
189
        $folder = $this->fullPath($folder);
190
        $files = $this->list($folder);
191
192
        return collect($files)
193
            ->map(function ($path, $key) use ($folder, $files) {
194
                $filename = str_replace($folder, '', $path);
195
                $exploded_name = explode('_', $filename);
196
                $index = array_shift($exploded_name);
197
                $last = count($files) - 1 == $key;
198
199
                return new Chunk(intval($index), $path, $this->disk, $last);
200
            })->sortBy(function (Chunk $chunk) {
201
                return $chunk->getIndex();
202
            });
203
    }
204
205
    /**
206
     * @param string $path
207
     * @return bool
208
     */
209
    public function exists(string $path): bool
210
    {
211
        return $this->filesystem()->disk($this->disk)->exists($path);
212
    }
213
214
    /**
215
     * @param string $folder
216
     * @return int
217
     */
218
    public function chunksCount(string $folder): int
219
    {
220
        $folder = $this->fullPath($folder);
221
222
        return count($this->list($folder));
223
    }
224
225
    /**
226
     * @param string $path
227
     * @return int
228
     */
229
    public function chunkSize(string $path): int
230
    {
231
        return $this->filesystem()->disk($this->disk)->size($path);
232
    }
233
234
    /**
235
     * @param $path
236
     * @return resource|null
237
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
238
     */
239
    public function readChunk($path)
240
    {
241
        return $this->filesystem()->disk($this->disk)->readStream($path);
242
    }
243
244
    /**
245
     * @param string $folder
246
     * @param \Illuminate\Support\Collection $chunks
247
     * @return \Illuminate\Support\Collection
248
     */
249
    public function createTemporaryFiles(string $folder, Collection $chunks): Collection
250
    {
251
        $this->addTemporaryContext($folder);
252
253
        return $chunks->map(function (Chunk $chunk) use ($folder) {
254
            $resource = $this->filesystem()->disk($this->disk)->readStream($chunk->getPath());
255
            $location = $this->temporaryFilesystem()->createTemporaryFile($folder);
256
257
            $stream = fopen($location, 'w+b');
258
259
            if (! $stream || stream_copy_to_stream($resource, $stream) === false || ! fclose($stream)) {
0 ignored issues
show
introduced by
$stream is of type false|resource, thus it always evaluated to false.
Loading history...
260
                return false;
261
            }
262
263
            return $location;
264
        });
265
    }
266
267
    /**
268
     * Concatenate all chunks into final merge.
269
     *
270
     * @param string $chunk
271
     * @param array $chunks
272
     * @return bool
273
     */
274
    public function concatenate(string $destination, array $chunks): bool
275
    {
276
        $first = Arr::first($chunks);
277
        $stream = new AppendStream;
278
279
        foreach ($chunks as $chunk) {
280
            if(!$this->isLocal()) {
281
                $temp = fopen($chunk, 'rb');
282
                $stream->append($temp);
0 ignored issues
show
Bug introduced by
It seems like $temp can also be of type false; however, parameter $stream of Keven\AppendStream\AppendStream::append() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

282
                $stream->append(/** @scrutinizer ignore-type */ $temp);
Loading history...
283
            } else {
284
                $stream->append($this->filesystem()->disk($this->disk)->readStream($chunk));
285
            }
286
        }
287
288
        if(!$this->isLocal()) {
289
            file_put_contents($first, $stream->getResource());
290
            return $this->filesystem()->disk($this->disk)->put($destination, fopen($first, 'rb'));
0 ignored issues
show
Bug introduced by
It seems like fopen($first, 'rb') can also be of type false; however, parameter $contents of Illuminate\Filesystem\FilesystemAdapter::put() does only seem to accept resource|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

290
            return $this->filesystem()->disk($this->disk)->put($destination, /** @scrutinizer ignore-type */ fopen($first, 'rb'));
Loading history...
291
        }
292
293
        return $this->filesystem()->disk($this->disk)->put($destination, $stream->getResource());
294
    }
295
}
296