Passed
Push — master ( 1690b4...a30145 )
by Webysther
02:31
created

Clean::showRemovedPackages()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 0
dl 0
loc 9
ccs 0
cts 8
cp 0
crap 12
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\Command;
13
14
use Symfony\Component\Console\Input\InputInterface;
15
use Symfony\Component\Console\Input\InputOption;
16
use Symfony\Component\Console\Output\OutputInterface;
17
use FilesystemIterator;
18
use stdClass;
19
20
/**
21
 * Clean mirror outdated files.
22
 *
23
 * @author Webysther Nunes <[email protected]>
24
 */
25
class Clean extends Base
26
{
27
    /**
28
     * Console description.
29
     *
30
     * @var string
31
     */
32
    protected $description = 'Clean outdated files of mirror';
33
34
    /**
35
     * Packages to verify first.
36
     *
37
     * @var array
38
     */
39
    protected $packages = [];
40
41
    /**
42
     * Console params configuration.
43
     */
44
    protected function configure():void
45
    {
46
        parent::configure();
47
        $this->setName('clean')
48
             ->setDescription($this->description)
49
             ->addOption(
50
                 'scrub',
51
                 null,
52
                 InputOption::VALUE_NONE,
53
                 'Check all directories for old files, use only to check all disk'
54
             );
55
    }
56
57
    /**
58
     * Execution.
59
     *
60
     * @param InputInterface  $input  Input console
61
     * @param OutputInterface $output Output console
62
     *
63
     * @return int 0 if pass, any another is error
64
     */
65
    public function childExecute(InputInterface $input, OutputInterface $output):int
66
    {
67
        if (!$this->flush($input, $output)) {
68
            return 1;
69
        }
70
71
        if (!count($this->changed)) {
72
            $output->writeln('Nothing to clean');
73
        }
74
75
        return 0;
76
    }
77
78
    /**
79
     * Flush old files.
80
     *
81
     * @param InputInterface  $input  Input console
82
     * @param OutputInterface $output Output console
83
     *
84
     * @return bool True if work, false otherside
85
     */
86
    public function flush(InputInterface $input, OutputInterface $output):bool
87
    {
88
        $this->input = $input;
0 ignored issues
show
Bug Best Practice introduced by
The property input does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
89
        $this->output = $output;
0 ignored issues
show
Bug Best Practice introduced by
The property output does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
90
91
        $this->determineMode();
92
93
        if (!$this->flushProviders()) {
94
            return false;
95
        }
96
97
        if (!$this->flushPackages()) {
98
            return false;
99
        }
100
101
        return true;
102
    }
103
104
    /**
105
     * Add information about how package is checked.
106
     *
107
     * @param array $list List of name packages
108
     */
109
    public function setChangedPackage(array $list):void
110
    {
111
        $this->packages = $list;
112
    }
113
114
    /**
115
     * Flush old cached files of providers.
116
     *
117
     * @return bool True if work, false otherside
118
     */
119
    protected function flushProviders():bool
120
    {
121
        $cachedir = getenv('PUBLIC_DIR').'/';
122
        $packages = $cachedir.'packages.json.gz';
123
124
        $json = gzdecode(file_get_contents($packages));
125
        $providers = json_decode($json);
126
        $includes = $providers->{'provider-includes'};
127
        $this->hasInit = file_exists($cachedir.'.init');
0 ignored issues
show
Bug Best Practice introduced by
The property hasInit does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
128
        $this->countedFolder = [];
0 ignored issues
show
Bug Best Practice introduced by
The property countedFolder does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
129
        $this->changed = [];
0 ignored issues
show
Bug Best Practice introduced by
The property changed does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
130
131
        $scrub = false;
132
        if ($this->input->hasOption('scrub') && $this->input->getOption('scrub')) {
133
            $scrub = true;
134
        }
135
136
        foreach ($includes as $template => $hash) {
137
            $fileurl = $cachedir.str_replace('%hash%', '*', $template).'.gz';
138
            $glob = glob($fileurl, GLOB_NOSORT);
139
140
            $this->output->writeln(
141
                'Check old file of <info>'.
142
                $fileurl.
143
                '</>'
144
            );
145
146
            // If have files and more than 1 to exists old ones
147
            if (count($glob) > 1 || $scrub) {
148
                $fileurlCurrent = $cachedir;
149
                $fileurlCurrent .= str_replace(
150
                    '%hash%',
151
                    $hash->sha256,
152
                    $template
153
                ).'.gz';
154
155
                $this->changed[] = $fileurlCurrent;
156
157
                foreach ($glob as $file) {
158
                    if ($file == $fileurlCurrent) {
159
                        continue;
160
                    }
161
162
                    if ($this->output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL) {
163
                        $this->output->writeln(
164
                            'Old provider <fg=blue;>'.$file.'</> was removed!'
165
                        );
166
                    }
167
168
                    unlink($file);
169
                    $this->removeLink($file);
170
                }
171
            }
172
        }
173
174
        return true;
175
    }
176
177
    /**
178
     * Flush old cached files of packages.
179
     *
180
     * @return bool True if work, false otherside
181
     */
182
    protected function flushPackages():bool
183
    {
184
        $increment = 0;
185
186
        foreach ($this->changed as $urlProvider) {
187
            $provider = json_decode(
188
                $this->unparseGzip(file_get_contents($urlProvider))
189
            );
190
            $list = $provider->providers;
191
            $total = count((array) $list);
192
            ++$increment;
193
194
            $this->output->writeln(
195
                '['.$increment.'/'.count($this->changed).'] '.
196
                'Check old packages for provider '.
197
                '<info>'.$this->shortname($urlProvider).'</>'
198
            );
199
            $this->progressBarStart($total);
200
            $this->flushPackage($list);
201
            $this->progressBarFinish();
202
            $this->showRemovedPackages();
203
        }
204
205
        return true;
206
    }
207
208
    /**
209
     * Flush from one provider.
210
     *
211
     * @param stdClass $list List of packages
212
     */
213
    protected function flushPackage(stdClass $list):void
214
    {
215
        $base = getenv('PUBLIC_DIR').'/p/';
216
        $countPackages = (bool) count($this->packages);
217
218
        $this->packageRemoved = [];
0 ignored issues
show
Bug Best Practice introduced by
The property packageRemoved does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
219
        foreach ($list as $name => $hash) {
220
            $this->progressBarUpdate();
221
222
            if ($this->hasInit) {
223
                continue;
224
            }
225
226
            $folder = dirname($name);
227
228
            // This folder was changed by last download?
229
            if ($countPackages && !in_array($base.$folder, $this->packages)) {
230
                continue;
231
            }
232
233
            // If only have the file and link dont exist old files
234
            if ($this->countFiles($base.$folder) < 3) {
235
                continue;
236
            }
237
238
            $glob = glob($base.$name.'$*.json.gz', GLOB_NOSORT);
239
240
            // If only have the file dont exist old files
241
            if (count($glob) < 2) {
242
                continue;
243
            }
244
245
            // Remove current value
246
            $file = $base.$name.'$'.$hash->sha256.'.json.gz';
247
            $glob = array_diff($glob, [$file]);
248
            foreach ($glob as $file) {
249
                $this->packageRemoved[] = $file;
250
                unlink($file);
251
                $this->removeLink($file);
252
            }
253
        }
254
    }
255
256
    /**
257
     * Count files inside folder.
258
     *
259
     * @param string $folder
260
     *
261
     * @return int
262
     */
263
    protected function countFiles(string $folder):int
264
    {
265
        if (isset($this->countedFolder[$folder])) {
266
            return $this->countedFolder[$folder];
267
        }
268
269
        $this->countedFolder[$folder] = iterator_count(
0 ignored issues
show
Bug Best Practice introduced by
The property countedFolder does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
270
            new FilesystemIterator($folder, FilesystemIterator::SKIP_DOTS)
271
        );
272
273
        return $this->countedFolder[$folder];
274
    }
275
276
    /**
277
     * Show packages removed.
278
     */
279
    protected function showRemovedPackages():void
280
    {
281
        if ($this->output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
282
            return;
283
        }
284
285
        foreach ($this->packageRemoved as $file) {
286
            $this->output->writeln(
287
                'Old package <fg=blue;>'.$file.'</> was removed!'
288
            );
289
        }
290
    }
291
292
    /**
293
     * Remove a simbolic link.
294
     *
295
     * @param string $path Path to file
296
     */
297
    protected function removeLink(string $target):void
298
    {
299
        // From .json.gz to .json
300
        $link = substr($target, 0, -3);
301
        if (is_link($link)) {
302
            unlink($link);
303
        }
304
    }
305
}
306