Completed
Push — master ( 9a3147...33bee6 )
by Webysther
02:43
created

Filesystem::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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