Passed
Branch tus-concatenation (9e8141)
by Ankit
02:26
created

File::setName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 1
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace TusPhp;
4
5
use Carbon\Carbon;
6
use TusPhp\Cache\Cacheable;
7
use TusPhp\Exception\FileException;
8
use TusPhp\Exception\ConnectionException;
9
use TusPhp\Exception\OutOfRangeException;
10
11
class File
12
{
13
    /** @const Max chunk size */
14
    const CHUNK_SIZE = 8192; // 8 bytes
15
16
    /** @const Input stream */
17
    const INPUT_STREAM = 'php://input';
18
19
    /** @const Read binary mode */
20
    const READ_BINARY = 'rb';
21
22
    /** @const Append binary mode */
23
    const APPEND_BINARY = 'ab+';
24
25
    /** @var string */
26
    protected $checksum;
27
28
    /** @var string */
29
    protected $name;
30
31
    /** @var Cacheable */
32
    protected $cache;
33
34
    /** @var int */
35
    protected $offset;
36
37
    /** @var string */
38
    protected $location;
39
40
    /** @var string */
41
    protected $filePath;
42
43
    /** @var int */
44
    protected $fileSize;
45
46
    /**
47
     * File constructor.
48
     *
49
     * @param string|null    $name
50
     * @param Cacheable|null $cache
51
     */
52 1
    public function __construct(string $name = null, Cacheable $cache = null)
53
    {
54 1
        $this->name  = $name;
55 1
        $this->cache = $cache;
56 1
    }
57
58
    /**
59
     * Set file meta.
60
     *
61
     * @param int    $offset
62
     * @param int    $fileSize
63
     * @param string $filePath
64
     * @param string $location
65
     *
66
     * @return File
67
     */
68 1
    public function setMeta(int $offset, int $fileSize, string $filePath, string $location = null) : self
69
    {
70 1
        $this->offset   = $offset;
71 1
        $this->fileSize = $fileSize;
72 1
        $this->filePath = $filePath;
73 1
        $this->location = $location;
74
75 1
        return $this;
76
    }
77
78
    /**
79
     * Set name.
80
     *
81
     * @param string $name
82
     *
83
     * @return File
84
     */
85 1
    public function setName(string $name) : self
86
    {
87 1
        $this->name = $name;
88
89 1
        return $this;
90
    }
91
92
    /**
93
     * Get name.
94
     *
95
     * @return string
96
     */
97 1
    public function getName() : string
98
    {
99 1
        return $this->name;
100
    }
101
102
    /**
103
     * Set file size.
104
     *
105
     * @param int $size
106
     *
107
     * @return File
108
     */
109 1
    public function setFileSize(int $size) : self
110
    {
111 1
        $this->fileSize = $size;
112
113 1
        return $this;
114
    }
115
116
    /**
117
     * Get file size.
118
     *
119
     * @return int
120
     */
121 1
    public function getFileSize() : int
122
    {
123 1
        return $this->fileSize;
124
    }
125
126
    /**
127
     * Set checksum.
128
     *
129
     * @param string $checksum
130
     *
131
     * @return File
132
     */
133 1
    public function setChecksum(string $checksum) : self
134
    {
135 1
        $this->checksum = $checksum;
136
137 1
        return $this;
138
    }
139
140
    /**
141
     * Get checksum.
142
     *
143
     * @return string
144
     */
145 1
    public function getChecksum() : string
146
    {
147 1
        return $this->checksum;
148
    }
149
150
    /**
151
     * Set offset.
152
     *
153
     * @param int $offset
154
     *
155
     * @return File
156
     */
157 1
    public function setOffset(int $offset) : self
158
    {
159 1
        $this->offset = $offset;
160
161 1
        return $this;
162
    }
163
164
    /**
165
     * Get offset.
166
     *
167
     * @return int
168
     */
169 1
    public function getOffset() : int
170
    {
171 1
        return $this->offset;
172
    }
173
174
    /**
175
     * Set location.
176
     *
177
     * @param string $location
178
     *
179
     * @return File
180
     */
181 1
    public function setLocation(string $location) : self
182
    {
183 1
        $this->location = $location;
184
185 1
        return $this;
186
    }
187
188
    /**
189
     * Get location.
190
     *
191
     * @return string
192
     */
193 1
    public function getLocation() : string
194
    {
195 1
        return $this->location;
196
    }
197
198
    /**
199
     * Set absolute file location.
200
     *
201
     * @param string $path
202
     *
203
     * @return File
204
     */
205 1
    public function setFilePath(string $path) : self
206
    {
207 1
        $this->filePath = $path;
208
209 1
        return $this;
210
    }
211
212
    /**
213
     * Get absolute location.
214
     *
215
     * @return string
216
     */
217 1
    public function getFilePath() : string
218
    {
219 1
        return $this->filePath;
220
    }
221
222
    /**
223
     * Get input stream.
224
     *
225
     * @return string
226
     */
227 1
    public function getInputStream() : string
228
    {
229 1
        return self::INPUT_STREAM;
230
    }
231
232
    /**
233
     * Get file meta.
234
     *
235
     * @return array
236
     */
237 1
    public function details() : array
238
    {
239 1
        $now = Carbon::now();
240
241
        return [
242 1
            'name' => $this->name,
243 1
            'size' => $this->fileSize,
244 1
            'offset' => $this->offset,
245 1
            'location' => $this->location,
246 1
            'file_path' => $this->filePath,
247 1
            'created_at' => $now->format($this->cache::RFC_7231),
248 1
            'expires_at' => $now->addSeconds($this->cache->getTtl())->format($this->cache::RFC_7231),
249
        ];
250
    }
251
252
    /**
253
     * Upload file to server.
254
     *
255
     * @param int $totalBytes
256
     *
257
     * @throws ConnectionException
258
     *
259
     * @return int
260
     */
261 6
    public function upload(int $totalBytes) : int
262
    {
263 6
        $bytesWritten = $this->offset;
264
265 6
        if ($bytesWritten === $totalBytes) {
266 1
            return $bytesWritten;
267
        }
268
269 5
        $input    = $this->open($this->getInputStream(), self::READ_BINARY);
270 5
        $output   = $this->open($this->getFilePath(), self::APPEND_BINARY);
271 4
        $checksum = $this->getChecksum();
272
273
        try {
274 4
            $this->seek($output, $bytesWritten);
275
276 4
            while ( ! feof($input)) {
277 4
                if (CONNECTION_NORMAL !== connection_status()) {
278 1
                    throw new ConnectionException('Connection aborted by user.');
279
                }
280
281 3
                $data  = $this->read($input, self::CHUNK_SIZE);
282 3
                $bytes = $this->write($output, $data, self::CHUNK_SIZE);
283
284 3
                $bytesWritten += $bytes;
285
286 3
                $this->cache->set($checksum, ['offset' => $bytesWritten]);
287
288 3
                if ($bytesWritten > $totalBytes) {
289 1
                    throw new OutOfRangeException('The uploaded file is corrupt.');
290
                }
291
292 2
                if ($bytesWritten === $totalBytes) {
293 2
                    break;
294
                }
295
            }
296 2
        } finally {
297 4
            $this->close($input);
298 4
            $this->close($output);
299
        }
300
301 2
        return $bytesWritten;
302
    }
303
304
    /**
305
     * Open file in append binary mode.
306
     *
307
     * @param string $filePath
308
     * @param string $mode
309
     *
310
     * @throws FileException
311
     *
312
     * @return resource
313
     */
314 6
    public function open(string $filePath, string $mode)
315
    {
316 6
        $this->exists($filePath, $mode);
317
318 5
        $ptr = @fopen($filePath, $mode);
319
320 5
        if (false === $ptr) {
321 2
            throw new FileException("Unable to open $filePath.");
322
        }
323
324 4
        return $ptr;
325
    }
326
327
    /**
328
     * Check if file to read exists.
329
     *
330
     * @param string $filePath
331
     * @param string $mode
332
     *
333
     * @throws FileException
334
     *
335
     * @return bool
336
     */
337 5
    public function exists(string $filePath, string $mode = self::READ_BINARY) : bool
338
    {
339 5
        if (self::INPUT_STREAM === $filePath) {
340 1
            return true;
341
        }
342
343 5
        if (self::READ_BINARY === $mode && ! file_exists($filePath)) {
344 2
            throw new FileException('File not found.');
345
        }
346
347 3
        return true;
348
    }
349
350
    /**
351
     * @param Resource $handle
352
     * @param int      $offset
353
     * @param int      $whence
354
     *
355
     * @throws FileException
356
     *
357
     * @return int
358
     */
359 3
    public function seek($handle, int $offset, int $whence = SEEK_SET) : int
360
    {
361 3
        $position = fseek($handle, $offset, $whence);
362
363 3
        if (-1 === $position) {
364 1
            throw new FileException('Cannot move pointer to desired position.');
365
        }
366
367 2
        return $position;
368
    }
369
370
    /**
371
     * Read data from file.
372
     *
373
     * @param Resource $handle
374
     * @param int      $chunkSize
375
     *
376
     * @throws FileException
377
     *
378
     * @return string
379
     */
380 3
    public function read($handle, int $chunkSize) : string
381
    {
382 3
        $data = fread($handle, $chunkSize);
383
384 3
        if (false === $data) {
385 1
            throw new FileException('Cannot read file.');
386
        }
387
388 2
        return (string) $data;
389
    }
390
391
    /**
392
     * Write data to file.
393
     *
394
     * @param Resource $handle
395
     * @param string   $data
396
     * @param int|null $length
397
     *
398
     * @throws FileException
399
     *
400
     * @return int
401
     */
402 2
    public function write($handle, string $data, $length = null) : int
403
    {
404 2
        $bytesWritten = is_int($length) ? fwrite($handle, $data, $length) : fwrite($handle, $data);
405
406 2
        if (false === $bytesWritten) {
0 ignored issues
show
introduced by
The condition false === $bytesWritten can never be true.
Loading history...
407 1
            throw new FileException('Cannot write to a file.');
408
        }
409
410 1
        return $bytesWritten;
411
    }
412
413
    /**
414
     * Merge 2 or more files.
415
     *
416
     * @param array $files File data with meta info.
417
     *
418
     * @return int
419
     */
420 3
    public function merge(array $files) : int
421
    {
422 3
        $destination = $this->getFilePath();
423 3
        $firstFile   = array_shift($files);
424
425
        // First partial file can directly be copied.
426 3
        $this->copy($firstFile['file_path'], $destination);
427
428 2
        $this->offset   = $firstFile['offset'];
429 2
        $this->fileSize = filesize($firstFile['file_path']);
430
431 2
        $handle = $this->open($destination, self::APPEND_BINARY);
432
433 2
        foreach ($files as $file) {
434 2
            if ( ! file_exists($file['file_path'])) {
435 1
                throw new FileException('File to be merged not found.');
436
            }
437
438 1
            $this->fileSize += $this->write($handle, file_get_contents($file['file_path']));
439
440 1
            $this->offset += $file['offset'];
441
        }
442
443 1
        $this->close($handle);
444
445 1
        return $this->fileSize;
446
    }
447
448
    /**
449
     * Copy file from source to destination.
450
     *
451
     * @param string $source
452
     * @param string $destination
453
     *
454
     * @return bool
455
     */
456 2
    public function copy(string $source, string $destination) : bool
457
    {
458 2
        $status = @copy($source, $destination);
459
460 2
        if (false === $status) {
461 1
            throw new FileException('Cannot copy source to destination.');
462
        }
463
464 1
        return $status;
465
    }
466
467
    /**
468
     * Delete file and/or folder.
469
     *
470
     * @param array $files
471
     * @param bool  $folder
472
     *
473
     * @return bool
474
     */
475 4
    public function delete(array $files, bool $folder = false) : bool
476
    {
477 4
        $status = $this->deleteFiles($files);
478
479 4
        if ($status && $folder) {
480 2
            return rmdir(dirname(current($files)));
481
        }
482
483 2
        return $status;
484
    }
485
486
    /**
487
     * Delete files.
488
     *
489
     * @param array $files
490
     *
491
     * @return bool
492
     */
493 3
    public function deleteFiles(array $files)
494
    {
495 3
        if (empty($files)) {
496 1
            return false;
497
        }
498
499 2
        $status = true;
500
501 2
        foreach ($files as $file) {
502 2
            if (file_exists($file)) {
503 2
                $status = $status && unlink($file);
504
            }
505
        }
506
507 2
        return $status;
508
    }
509
510
    /**
511
     * Close file.
512
     *
513
     * @param $handle
514
     *
515
     * @return bool
516
     */
517 4
    public function close($handle) : bool
518
    {
519 4
        return fclose($handle);
520
    }
521
}
522