Completed
Push — feature/decoupled ( 5e8293...cc31bd )
by Webysther
02:21
created

Filesystem::getFolderCount()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 12
nc 3
nop 1
dl 0
loc 22
rs 9.2
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 FlysystemFilesystem
0 ignored issues
show
Bug introduced by
The type Webs\Mirror\FlysystemFilesystem was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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);
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);
0 ignored issues
show
Documentation Bug introduced by
It seems like new League\Flysystem\Filesystem($adapter) of type League\Flysystem\Filesystem is incompatible with the declared type Webs\Mirror\FlysystemFilesystem of property $filesystem.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
66
    }
67
68
    /**
69
     * Normalize path to use .gz.
70
     *
71
     * @param string $path
72
     *
73
     * @return string
74
     */
75
    public function normalize(string $path):string
76
    {
77
        $fullPath = $this->getFullPath($path);
78
79
        if (is_link($fullPath)) {
80
            return $path;
81
        }
82
83
        $extension = pathinfo($fullPath, PATHINFO_EXTENSION);
84
85
        if ($extension != 'json') {
86
            return $path;
87
        }
88
89
        if ($extension == 'json') {
90
            return $path.'.gz';
91
        }
92
93
        return $path;
94
    }
95
96
    /**
97
     * Decode from gz after read from disk.
98
     *
99
     * @see FlyFilesystem::read
100
     */
101
    public function read(string $path):string
102
    {
103
        $path = $this->normalize($path);
104
105
        return $this->decode($this->filesystem->read($path));
106
    }
107
108
    /**
109
     * Encode to gz before write to disk with hash checking.
110
     *
111
     * @see FlyFilesystem::write
112
     */
113
    public function write(string $path, string $contents):Filesystem
114
    {
115
        $path = $this->normalize($path);
116
        $this->filesystem->put($path, $this->encode($contents));
117
118
        if ($this->getHash($contents) != $this->getHashFile($path)) {
119
            $this->filesystem->delete($path);
120
            throw new Exception("Write file $path hash failed");
121
        }
122
123
        if (strpos($path, '.json.gz') !== false) {
124
            $this->symlink($path, substr($path, 0, -3));
125
        }
126
127
        return $this;
128
    }
129
130
    /**
131
     * Simple touch.
132
     *
133
     * @param string $path
134
     *
135
     * @return Filesystem
136
     */
137
    public function touch(string $path):Filesystem
138
    {
139
        if (file_exists($this->getFullPath($path))) {
140
            return $this;
141
        }
142
143
        $this->filesystem->write($path, '');
144
145
        return $this;
146
    }
147
148
    /**
149
     * Create a symlink.
150
     *
151
     * @param string $file
152
     * @param string $link
153
     *
154
     * @return Filesystem
155
     */
156
    protected function symlink(string $file, string $link):Filesystem
157
    {
158
        if (!$this->has($file)) {
159
            throw new Exception("File $file not found");
160
        }
161
162
        $link = $this->getFullPath($link);
163
        if (file_exists($link)) {
164
            unlink($link);
165
        }
166
167
        symlink(basename($file), $link);
168
169
        return $this;
170
    }
171
172
    /**
173
     * @see FlyFilesystem::has
174
     */
175
    public function has(string $path):bool
176
    {
177
        try {
178
            return $this->filesystem->has($this->normalize($path));
179
        } catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
180
        }
181
182
        return false;
183
    }
184
185
    /**
186
     * Rename less strict.
187
     *
188
     * @param string $from
189
     * @param string $target
190
     *
191
     * @return Filesystem
192
     */
193
    public function move(string $from, string $target):Filesystem
194
    {
195
        $targetGz = $this->normalize($target);
196
        $this->filesystem->rename($this->normalize($from), $targetGz);
197
        $this->symlink($targetGz, $target);
198
199
        return $this;
200
    }
201
202
    /**
203
     * @see FlyFilesystem::delete
204
     * @see FlyFilesystem::deleteDir
205
     */
206
    public function delete(string $fileOrDirectory):Filesystem
207
    {
208
        $path = $this->getFullPath($fileOrDirectory);
209
210
        if (is_dir($path)) {
211
            $this->filesystem->deleteDir($fileOrDirectory);
212
213
            return $this;
214
        }
215
216
        if (is_link($path)) {
217
            unlink($path);
218
219
            return $this;
220
        }
221
222
        $fileOrDirectory = $this->normalize($fileOrDirectory);
223
        $path = $this->normalize($path);
224
225
        $this->filesystem->delete($fileOrDirectory);
226
227
        $path = substr($path, 0, -3);
228
        if (is_link($path)) {
229
            unlink($path);
230
        }
231
232
        return $this;
233
    }
234
235
    /**
236
     * Glob without file sort.
237
     *
238
     * @param string $pattern
239
     *
240
     * @return array
241
     */
242
    public function glob(string $pattern):array
243
    {
244
        $return = glob($pattern, GLOB_NOSORT);
245
246
        if ($return === false) {
247
            return [];
248
        }
249
250
        return $return;
251
    }
252
253
    /**
254
     * Count files inside folder, if is a file, return 0.
255
     *
256
     * @param string $folder
257
     *
258
     * @return int
259
     */
260
    public function getCount(string $folder):int
261
    {
262
        $path = $this->getFullPath($folder);
263
        $hash = $this->getHash($path);
264
265
        if (!is_dir($path)) {
266
            return 0;
267
        }
268
269
        if (array_key_exists($hash, $this->countedFolder)) {
270
            return $this->countedFolder[$hash];
271
        }
272
273
        $iterator = new FilesystemIterator(
274
            $path,
275
            FilesystemIterator::SKIP_DOTS
276
        );
277
278
        $totalFiles = iterator_count($iterator);
279
        $this->countedFolder[$hash] = $totalFiles;
280
281
        return $totalFiles;
282
    }
283
284
    /**
285
     * Get full path.
286
     *
287
     * @param string $path
288
     *
289
     * @return string
290
     */
291
    protected function getFullPath(string $path):string
292
    {
293
        return $this->directory.DIRECTORY_SEPARATOR.$path;
294
    }
295
296
    /**
297
     * Calculates SHA256.
298
     *
299
     * @param string $string
300
     *
301
     * @return string
302
     */
303
    public function getHash(string $string):string
304
    {
305
        return hash('sha256', $string);
306
    }
307
308
    /**
309
     * Calculates SHA256 for file.
310
     *
311
     * @param string $path
312
     *
313
     * @return string
314
     */
315
    public function getHashFile(string $path):string
316
    {
317
        // dont use hash_file because content is saved with gz
318
        return $this->getHash($this->read($path));
319
    }
320
}
321