Passed
Push — feature/decoupled ( a10681...ef93d4 )
by Webysther
02:14
created

Filesystem::isGzFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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