Passed
Push — master ( a30145...bc128f )
by Webysther
36s
created

Filesystem::has()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 1
nc 2
nop 1
dl 0
loc 3
ccs 0
cts 3
cp 0
crap 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Packagist Mirror.
7
 *
8
 * For the full license information, please view the LICENSE.md
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Webs\Mirror;
13
14
use League\Flysystem\Filesystem as FlyFilesystem;
15
use League\Flysystem\Adapter\Local;
16
use Exception;
17
use FilesystemIterator;
18
19
/**
20
 * Middleware to access filesystem with transparent gz encode/decode.
21
 *
22
 * @author Webysther Nunes <[email protected]>
23
 */
24
class Filesystem
25
{
26
    use GZip;
27
28
    /**
29
     * @var FlyFilesystem
30
     */
31
    protected $filesystem;
32
33
    /**
34
     * @var string
35
     */
36
    protected $directory;
37
38
    /**
39
     * Ephemeral cache for folder files count.
40
     *
41
     * @var array
42
     */
43
    protected $countedFolder = [];
44
45
    /**
46
     * @param string $dir        Base directory
47
     * @param bool   $initialize If true initialize the filesystem access
48
     */
49
    public function __construct($baseDirectory)
50
    {
51
        $this->directory = realpath($baseDirectory).DIRECTORY_SEPARATOR;
52
53
        // Create the adapter
54
        $localAdapter = new Local($this->directory);
55
56
        // And use that to create the file system
57
        $this->filesystem = new FlyFilesystem($localAdapter);
58
    }
59
60
    /**
61
     * Add suffix gz to json file.
62
     *
63
     * @param string $path
64
     *
65
     * @return string
66
     */
67 View Code Duplication
    public function getGzName(string $path):string
68
    {
69
        $fullPath = $this->getFullPath($path);
70
        $extension = pathinfo($fullPath, PATHINFO_EXTENSION);
71
72
        if ($extension == 'json') {
73
            return $path.'.gz';
74
        }
75
76
        return $path;
77
    }
78
79
    /**
80
     * Get link name from gz.
81
     *
82
     * @param string $path
83
     *
84
     * @return string
85
     */
86 View Code Duplication
    protected function getLink(string $path):string
87
    {
88
        $fullPath = $this->getFullPath($path);
89
        $extension = pathinfo($fullPath, PATHINFO_EXTENSION);
90
91
        if ($extension == 'gz') {
92
            return substr($path, 0, -3);
93
        }
94
95
        return $path;
96
    }
97
98
    /**
99
     * Decode from gz after read from disk.
100
     *
101
     * @see FlyFilesystem::read
102
     */
103
    public function read(string $path):string
104
    {
105
        $path = $this->getGzName($path);
106
        $file = $this->filesystem->read($path);
107
108
        if ($file === false) {
109
            return '';
110
        }
111
112
        return (string) $this->decode($file);
113
    }
114
115
    /**
116
     * Encode to gz before write to disk with hash checking.
117
     *
118
     * @see FlyFilesystem::write
119
     */
120
    public function write(string $path, string $contents):Filesystem
121
    {
122
        $file = $this->getGzName($path);
123
        $this->filesystem->put($file, $this->encode($contents));
124
        $decoded = $this->decode($contents);
125
126
        if ($this->getHash($decoded) != $this->getHashFile($file)) {
127
            $this->filesystem->delete($file);
128
            throw new Exception("Write file $path hash failed");
129
        }
130
131
        $this->symlink($file);
132
133
        return $this;
134
    }
135
136
    /**
137
     * Simple touch.
138
     *
139
     * @param string $path
140
     *
141
     * @return Filesystem
142
     */
143
    public function touch(string $path):Filesystem
144
    {
145
        if ($this->has($path)) {
146
            return $this;
147
        }
148
149
        touch($this->getFullPath($path));
150
151
        return $this;
152
    }
153
154
    /**
155
     * @param string $file
156
     *
157
     * @return bool
158
     */
159
    protected function isGzFile(string $file):bool
160
    {
161
        if (substr($this->getGzName($file), -3) == '.gz') {
162
            return true;
163
        }
164
165
        return false;
166
    }
167
168
    /**
169
     * Create a symlink.
170
     *
171
     * @param string $file
172
     *
173
     * @return Filesystem
174
     */
175
    protected function symlink(string $file):Filesystem
176
    {
177
        if (!$this->hasFile($file) || !$this->isGzFile($file)) {
178
            return $this;
179
        }
180
181
        $path = $this->getGzName($file);
182
        $link = $this->getLink($path);
183
184
        if ($this->hasLink($link)) {
185
            return $this;
186
        }
187
188
        symlink(basename($path), $this->getFullPath($link));
189
190
        return $this;
191
    }
192
193
    /**
194
     * @see FlyFilesystem::has
195
     */
196
    public function has(string $path):bool
197
    {
198
        return $this->hasFile($path) && $this->hasLink($path);
199
    }
200
201
    /**
202
     * @see FlyFilesystem::has
203
     */
204
    public function hasFile(string $path):bool
205
    {
206
        return file_exists($this->getFullPath($this->getGzName($path)));
207
    }
208
209
    /**
210
     * @see FlyFilesystem::has
211
     */
212
    protected function hasLink(string $path):bool
213
    {
214
        return is_link($this->getFullPath($this->getLink($path)));
215
    }
216
217
    /**
218
     * Move to not dot name of file.
219
     *
220
     * @param string $from
221
     *
222
     * @return Filesystem
223
     */
224
    public function move(string $from):Filesystem
225
    {
226
        if (!$this->has($from)) {
227
            return $this;
228
        }
229
230
        $file = $this->getGzName($from);
231
        $target = substr($file, 1);
232
233
        if ($this->has($target)) {
234
            $this->delete($target);
235
        }
236
237
        retry(8, function () use ($from, $target) {
238
            $this->filesystem->rename($this->getGzName($from), $target);
239
        }, 250);
240
241
        $this->symlink($target);
242
        // remove old symlink
243
        $this->delete($from);
244
245
        return $this;
246
    }
247
248
    /**
249
     * @see FlyFilesystem::delete
250
     * @see FlyFilesystem::deleteDir
251
     */
252
    public function delete(string $fileOrDirectory):Filesystem
253
    {
254
        $path = $this->getFullPath($fileOrDirectory);
255
256
        if (is_dir($path)) {
257
            $this->filesystem->deleteDir($fileOrDirectory);
258
259
            return $this;
260
        }
261
262
        $file = $this->getGzName($path);
263
        if (file_exists($file)) {
264
            unlink($file);
265
        }
266
267
        $link = $this->getLink($path);
268
        if (is_link($link)) {
269
            unlink($link);
270
        }
271
272
        return $this;
273
    }
274
275
    /**
276
     * Glob without file sort.
277
     *
278
     * @param string $pattern
279
     *
280
     * @return array
281
     */
282
    public function glob(string $pattern):array
283
    {
284
        $return = glob($this->getFullPath($pattern), GLOB_NOSORT);
285
286
        if ($return === false) {
287
            return [];
288
        }
289
290
        return $return;
291
    }
292
293
    /**
294
     * Count files inside folder, if is a file, return 0.
295
     *
296
     * @param string $folder
297
     *
298
     * @return int
299
     */
300
    public function getCount(string $folder):int
301
    {
302
        $path = $this->getFullPath($folder);
303
        $hash = $this->getHash($path);
304
305
        if (!is_dir($path)) {
306
            $path = dirname($path);
307
        }
308
309
        if (array_key_exists($hash, $this->countedFolder)) {
310
            return $this->countedFolder[$hash];
311
        }
312
313
        $iterator = new FilesystemIterator(
314
            $path,
315
            FilesystemIterator::SKIP_DOTS
316
        );
317
318
        $totalFiles = iterator_count($iterator);
319
        $this->countedFolder[$hash] = $totalFiles;
320
321
        return $totalFiles;
322
    }
323
324
    /**
325
     * Get full path.
326
     *
327
     * @param string $path
328
     *
329
     * @return string
330
     */
331
    public function getFullPath(string $path):string
332
    {
333
        if (strpos($path, $this->directory) !== false) {
334
            return $path;
335
        }
336
337
        return $this->directory.$path;
338
    }
339
340
    /**
341
     * Calculates SHA256.
342
     *
343
     * @param string $string
344
     *
345
     * @return string
346
     */
347
    public function getHash(string $string):string
348
    {
349
        return hash('sha256', $string);
350
    }
351
352
    /**
353
     * Calculates SHA256 for file.
354
     *
355
     * @param string $path
356
     *
357
     * @return string
358
     */
359
    public function getHashFile(string $path):string
360
    {
361
        // dont use hash_file because content is saved with gz
362
        return $this->getHash($this->read($path));
363
    }
364
}
365