FilesystemCleaner   A
last analyzed

Complexity

Total Complexity 19

Size/Duplication

Total Lines 193
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 80
dl 0
loc 193
rs 10
c 0
b 0
f 0
wmc 19

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A getDownloadFoldersFinder() 0 11 1
A __invoke() 0 6 2
A previewRemovalOfFolders() 0 37 2
A removeFolders() 0 30 3
A getFoldersToRemove() 0 18 3
B shouldRemoveFolders() 0 32 7
1
<?php declare(strict_types=1);
2
3
namespace App\Filesystem;
4
5
use App\Domain\Path;
6
use App\UI\Skippable;
7
use App\UI\UserInterface;
8
use Symfony\Component\Filesystem\Filesystem;
9
use Symfony\Component\Finder\Finder;
10
11
final class FilesystemCleaner
12
{
13
    use Skippable;
14
15
    /** @var \App\UI\UserInterface */
16
    private $ui;
17
18
    /**
19
     * @param \App\UI\UserInterface $ui
20
     */
21
    public function __construct(UserInterface $ui)
22
    {
23
        $this->ui = $ui;
24
    }
25
26
    /**
27
     * @param \App\Domain\Path $downloadPath
28
     *
29
     * @throws \RuntimeException
30
     */
31
    public function __invoke(Path $downloadPath): void
32
    {
33
        $foldersToRemove = $this->getFoldersToRemove($downloadPath);
34
35
        if ($this->shouldRemoveFolders($foldersToRemove, $downloadPath)) {
36
            $this->removeFolders($foldersToRemove);
37
        }
38
    }
39
40
    /**
41
     * @param \App\Domain\Path $downloadPath
42
     *
43
     * @return \App\Filesystem\FilesystemObjects
44
     * @throws \RuntimeException
45
     */
46
    private function getFoldersToRemove(Path $downloadPath): FilesystemObjects
47
    {
48
        $foldersToRemove = new FilesystemObjects();
49
        try {
50
            // Look for empty folders and remove these
51
            $emptyFolders = $this->getDownloadFoldersFinder($downloadPath)
52
                ->filter(function (\SplFileInfo $folder) {
53
                    return !(new Finder())->files()->in($folder->getRealPath())->hasResults();
54
                });
55
56
            foreach ($emptyFolders->getIterator() as $folder) {
57
                $foldersToRemove->set($folder->getRealPath(), $folder);
58
            }
59
        } catch (\LogicException $e) {
60
            // Here we know that the download folder will exist.
61
        }
62
63
        return $foldersToRemove;
64
    }
65
66
    /**
67
     * @param \App\Domain\Path $downloadPath
68
     *
69
     * @return \Symfony\Component\Finder\Finder
70
     * @throws \InvalidArgumentException
71
     */
72
    private function getDownloadFoldersFinder(Path $downloadPath): Finder
73
    {
74
        return (new Finder())
75
            ->directories()
76
            ->in((string) $downloadPath)
77
            ->sort(function (\SplFileInfo $fileInfoA, \SplFileInfo $fileInfoB) {
78
                // Sort the result by folder depth
79
                $a = substr_count($fileInfoA->getRealPath(), DIRECTORY_SEPARATOR);
80
                $b = substr_count($fileInfoB->getRealPath(), DIRECTORY_SEPARATOR);
81
82
                return $a <=> $b;
83
            });
84
    }
85
86
    /**
87
     * @param \App\Filesystem\FilesystemObjects $foldersToRemove
88
     */
89
    private function removeFolders(FilesystemObjects $foldersToRemove): void
90
    {
91
        $errors = [];
92
        foreach ($foldersToRemove as $folderToRemove) {
93
            $relativeFolderPath = $folderToRemove->getRelativePathname();
94
95
            try {
96
                (new Filesystem())->remove($folderToRemove->getRealPath());
97
98
                $this->ui->writeln(
99
                    sprintf(
100
                        '%s* The folder <info>%s</info> has been removed.',
101
                        $this->ui->indent(2),
102
                        $relativeFolderPath
103
                    )
104
                );
105
            } catch (\Exception $e) {
106
                $this->ui->logError(
107
                    sprintf(
108
                        '%s* <error>The folder %s could not be removed.</error>',
109
                        $this->ui->indent(2),
110
                        $relativeFolderPath
111
                    ),
112
                    $errors
113
                );
114
            }
115
        }
116
        $this->ui->displayErrors($errors, 'the removal of folders', 'info', 1);
117
118
        $this->ui->writeln(PHP_EOL.'<info>Done.</info>'.PHP_EOL);
119
    }
120
121
    /**
122
     * @param \App\Filesystem\FilesystemObjects $foldersToRemove
123
     * @param \App\Domain\Path $downloadPath
124
     *
125
     * @return bool
126
     */
127
    private function shouldRemoveFolders(FilesystemObjects $foldersToRemove, Path $downloadPath): bool
128
    {
129
        $this->ui->write(
130
            sprintf(
131
                'Synchronize the <info>%s</info> folder with the downloaded contents... ',
132
                (string) $downloadPath
133
            )
134
        );
135
136
        if ($foldersToRemove->isEmpty()) {
137
            $this->ui->writeln('<info>Done.</info>');
138
139
            return false;
140
        }
141
142
        $this->ui->writeln(PHP_EOL);
143
144
        if (!$this->ui->isDryRun() && !$this->ui->isInteractive()) {
145
            return true;
146
        }
147
148
        $confirmationDefault = $this->previewRemovalOfFolders($foldersToRemove, $downloadPath);
149
150
        $this->ui->write($this->ui->indent());
151
152
        if ($this->skip($this->ui) || !$this->ui->confirm($confirmationDefault)) {
153
            $this->ui->writeln(($this->ui->isDryRun() ? '' : PHP_EOL).'<info>Done.</info>'.PHP_EOL);
154
155
            return false;
156
        }
157
158
        return true;
159
    }
160
161
    /**
162
     * @param \App\Filesystem\FilesystemObjects $foldersToRemove
163
     * @param \App\Domain\Path $downloadPath
164
     *
165
     * @return bool Returns whether or not the confirmation default should be true or false
166
     */
167
    private function previewRemovalOfFolders(FilesystemObjects $foldersToRemove, Path $downloadPath): bool
168
    {
169
        $nbFoldersToRemove = $foldersToRemove->count();
170
171
        if ($nbFoldersToRemove <= 10) {
172
            $this->ui->writeln(
173
                sprintf(
174
                    '%sThe script is about to remove the following folders from <info>%s</info>:',
175
                    $this->ui->indent(),
176
                    (string) $downloadPath
177
                )
178
            );
179
            $this->ui->listing(
180
                $foldersToRemove
181
                    ->map(function (\SplFileInfo $folder) use ($downloadPath) {
182
                        return sprintf(
183
                            '<info>%s</info>',
184
                            str_replace((string) $downloadPath.DIRECTORY_SEPARATOR, '', $folder->getRealPath())
185
                        );
186
                    })
187
                    ->toArray(),
188
                3
189
            );
190
191
            return true;
192
        }
193
194
        $this->ui->write(
195
            sprintf(
196
                '%sThe script is about to remove <question> %s </question> folders from <info>%s</info>. ',
197
                $this->ui->indent(),
198
                $nbFoldersToRemove,
199
                (string) $downloadPath
200
            )
201
        );
202
203
        return false;
204
    }
205
}
206