Filesystem::has()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 2

Importance

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