Test Failed
Push — master ( d08b1d...c7da01 )
by Filippo
14:13 queued 09:16
created

MergeHandler::dispatchMerge()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 14
c 1
b 0
f 0
nc 3
nop 2
dl 0
loc 21
rs 9.7998
1
<?php
2
3
namespace Jobtech\LaravelChunky\Handlers;
4
5
use Illuminate\Container\Container;
0 ignored issues
show
Bug introduced by
The type Illuminate\Container\Container was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use Illuminate\Support\Traits\ForwardsCalls;
7
use Jobtech\LaravelChunky\Chunk;
8
use Jobtech\LaravelChunky\Contracts\ChunkyManager;
9
use Jobtech\LaravelChunky\Contracts\MergeHandler as MergeHandlerContract;
10
use Jobtech\LaravelChunky\Events\ChunksMerged;
11
use Jobtech\LaravelChunky\Exceptions\ChunksIntegrityException;
12
use Jobtech\LaravelChunky\Exceptions\ChunkyException;
13
use Jobtech\LaravelChunky\Http\Requests\AddChunkRequest;
14
use Jobtech\LaravelChunky\Jobs\MergeChunks;
15
use Keven\AppendStream\AppendStream;
16
17
/**
18
 * @method \Jobtech\LaravelChunky\ChunkySettings settings()
19
 * @method \Jobtech\LaravelChunky\Support\ChunksFilesystem chunksFilesystem()
20
 * @method \Jobtech\LaravelChunky\Support\MergeFilesystem mergeFilesystem()
21
 * @method array mergeOptions()
22
 * @method \Illuminate\Support\Collection listChunks(?string $folder = null)
23
 * @method resource|null readChunk($chunk)
24
 * @method bool deleteChunks(string $folder)
25
 * @method bool isValidChunksFolder(string $folder)
26
 * @method bool checkChunksIntegrity(string $folder, int $chunk_size, int $total_size)
27
 */
28
class MergeHandler implements MergeHandlerContract
29
{
30
    use ForwardsCalls;
31
32
    private ?ChunkyManager $manager;
33
34
    public function __construct(?ChunkyManager $manager = null)
35
    {
36
        $this->manager = $manager;
37
    }
38
39
    /**
40
     * @param string $folder
41
     * @param string $target
42
     *
43
     * @throws \Illuminate\Contracts\Filesystem\FileExistsException|\Illuminate\Contracts\Filesystem\FileNotFoundException
44
     *
45
     * @return string
46
     */
47
    private function concatenate(string $folder, string $target): string
48
    {
49
        // Merge
50
        $chunks = $this->listChunks(
51
            $folder,
52
        )->map(function (Chunk $item) {
53
            return $item->getPath();
54
        });
55
56
        if (! $this->chunksFilesystem()->isLocal()) {
57
            return $this->temporaryConcatenate($target, $chunks->toArray());
58
        }
59
60
        $merge = $chunks->first();
61
        if (! $this->chunksFilesystem()->concatenate($merge, $chunks->toArray())) {
62
            throw new ChunkyException('Unable to concatenate chunks');
63
        }
64
65
        return $this->mergeFilesystem()
66
            ->store(
67
                $target,
68
                $this->readChunk($merge),
69
                $this->mergeOptions()
70
            );
71
    }
72
73
    /**
74
     * @param string $target
75
     * @param array $chunks
76
     * @return string
77
     * @throws \Illuminate\Contracts\Filesystem\FileExistsException
78
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
79
     */
80
    private function temporaryConcatenate(string $target, array $chunks)
81
    {
82
        $stream = new AppendStream;
83
        $tmp_streams = [];
84
85
        foreach ($chunks as $chunk) {
86
            $chunk_stream = $this->chunksFilesystem()->readChunk($chunk);
87
            $chunk_contents = stream_get_contents($chunk_stream);
88
            fclose($chunk_stream);
89
90
            $tmp_stream = tmpfile();
91
            fwrite($tmp_stream, $chunk_contents);
0 ignored issues
show
Bug introduced by
It seems like $tmp_stream can also be of type false; however, parameter $handle of fwrite() 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

91
            fwrite(/** @scrutinizer ignore-type */ $tmp_stream, $chunk_contents);
Loading history...
92
            rewind($tmp_stream);
0 ignored issues
show
Bug introduced by
It seems like $tmp_stream can also be of type false; however, parameter $handle of rewind() 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

92
            rewind(/** @scrutinizer ignore-type */ $tmp_stream);
Loading history...
93
            $stream->append($tmp_stream);
0 ignored issues
show
Bug introduced by
It seems like $tmp_stream 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

93
            $stream->append(/** @scrutinizer ignore-type */ $tmp_stream);
Loading history...
94
            $tmp_streams[] = $tmp_stream;
95
        }
96
97
        $path = $this->mergeFilesystem()->store($target, $stream->getResource(), $this->mergeOptions());
98
99
        foreach ($tmp_streams as $stream) {
100
            if (is_resource($stream)) {
101
                fclose($stream);
102
            }
103
        }
104
105
        return $path;
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     *
111
     * @codeCoverageIgnore
112
     */
113
    public function setManager(ChunkyManager $manager): MergeHandler
114
    {
115
        $this->manager = $manager;
116
117
        return $this;
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     *
123
     * @codeCoverageIgnore
124
     */
125
    public function manager(): ChunkyManager
126
    {
127
        if ($this->manager === null) {
128
            $this->manager = Container::getInstance()->make('chunky');
129
        }
130
131
        return $this->manager;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->manager could return the type null which is incompatible with the type-hinted return Jobtech\LaravelChunky\Contracts\ChunkyManager. Consider adding an additional type-check to rule them out.
Loading history...
132
    }
133
134
    /**
135
     * {@inheritdoc}
136
     */
137
    public function dispatchMerge(AddChunkRequest $request, string $folder)
138
    {
139
        $merge_path = $request->fileInput()->getClientOriginalName();
140
        $chunk_size = $request->chunkSizeInput();
141
        $total_size = $request->totalSizeInput();
142
143
        if (empty($connection = $this->settings()->connection())) {
144
            if (! $this->checkChunksIntegrity($folder, $chunk_size, $total_size)) {
145
                throw new ChunksIntegrityException('Chunks total file size doesnt match with original file size');
146
            }
147
148
            return $this->merge($folder, $merge_path);
149
        }
150
151
        return MergeChunks::dispatch(
152
            $folder,
153
            $merge_path,
154
            $chunk_size,
155
            $total_size
156
        )->onConnection($connection)
157
            ->onQueue($this->settings()->queue());
158
    }
159
160
    /**
161
     * {@inheritdoc}
162
     */
163
    public function merge(string $chunks_folder, string $merge_path): string
164
    {
165
        // Check chunks folder
166
        if (! $this->isValidChunksFolder($chunks_folder)) {
167
            throw new ChunkyException("Invalid chunks folder {$chunks_folder}");
168
        }
169
170
        /** @var resource $origin */
171
        $path = $this->concatenate($chunks_folder, $merge_path);
172
173
        // Final check and cleanup
174
        if (! $path) {
175
            throw new ChunkyException('An error occurred while moving merge to destination');
176
        }
177
178
        $this->deleteChunks($chunks_folder);
179
180
        event(new ChunksMerged($path));
0 ignored issues
show
Bug introduced by
The function event was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

180
        /** @scrutinizer ignore-call */ 
181
        event(new ChunksMerged($path));
Loading history...
181
182
        return $path;
183
    }
184
185
    /**
186
     * {@inheritdoc}
187
     */
188
    public static function instance(?ChunkyManager $manager = null): MergeHandlerContract
189
    {
190
        return new static($manager);
191
    }
192
193
    public function __call($method, $parameters)
194
    {
195
        if (! method_exists($this, $method)) {
196
            return $this->forwardCallTo($this->manager(), $method, $parameters);
197
        }
198
199
        return $this->{$method}(...$parameters);
200
    }
201
}
202