Completed
Push — master ( 98be7b...7a3330 )
by Dorian
01:30
created

FilesystemManager::getFoldersToRemove()   B

Complexity

Conditions 5
Paths 11

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 10
nc 11
nop 2
1
<?php declare(strict_types=1);
2
3
namespace App\Platform\YouTube;
4
5
use App\Domain\Collection\FilesystemObjects;
6
use App\Domain\Collection\Path;
7
use App\UI\UserInterface;
8
use Symfony\Component\Filesystem\Filesystem;
9
use Symfony\Component\Finder\Finder;
10
11
trait FilesystemManager
12
{
13
    /** @var \App\UI\UserInterface */
14
    private $ui;
15
16
    /** @var array */
17
    private $options;
18
19
    /**
20
     * @param \App\UI\UserInterface $ui
21
     * @param array $options
22
     */
23
    public function __construct(UserInterface $ui, array $options)
24
    {
25
        $this->ui = $ui;
26
        $this->options = $options;
27
    }
28
29
    /**
30
     * @param \App\Domain\Collection\Path $downloadPath
31
     *
32
     * @return \Symfony\Component\Finder\Finder
33
     */
34
    abstract protected function getAllDownloadsFolderFinder(Path $downloadPath): Finder;
35
36
    /**
37
     * @param \App\Platform\YouTube\Download $download
38
     *
39
     * @return \Symfony\Component\Finder\Finder|\SplFileInfo[]
40
     * @throws \InvalidArgumentException
41
     */
42
    abstract protected function getDownloadFolderFinder(Download $download): Finder;
43
44
    /**
45
     * @return bool
46
     */
47
    abstract protected function skip(): bool;
48
49
    /**
50
     * {@inheritdoc}
51
     * @param \App\Platform\YouTube\Downloads $downloads
52
     *
53
     * @throws \RuntimeException
54
     */
55
    private function cleanFilesystem(Downloads $downloads, Path $downloadPath): void
56
    {
57
        $foldersToRemove = $this->getFoldersToRemove($downloads, $downloadPath);
58
59
        if ($this->shouldRemoveFolders($foldersToRemove, $downloadPath)) {
60
            $this->removeFolders($foldersToRemove, $downloadPath);
61
        }
62
    }
63
64
    /**
65
     * @param \App\Platform\YouTube\Downloads $downloads
66
     * @param \App\Domain\Collection\Path $downloadPath
67
     *
68
     * @return \App\Domain\Collection\FilesystemObjects
69
     * @throws \RuntimeException
70
     */
71
    private function getFoldersToRemove(Downloads $downloads, Path $downloadPath): FilesystemObjects
72
    {
73
        $foldersToRemove = new FilesystemObjects();
0 ignored issues
show
Bug introduced by
The call to FilesystemObjects::__construct() misses a required argument $elements.

This check looks for function calls that miss required arguments.

Loading history...
74
        try {
75
            $completedDownloadsFolders = $this->getCompletedDownloadsFolders($downloads);
76
77
            foreach ($this->getAllDownloadsFolderFinder($downloadPath)->getIterator() as $folder) {
78
                if (!$this->isFolderInCollection($folder, $foldersToRemove, true, $downloadPath) &&
79
                    !$this->isFolderInCollection($folder, $completedDownloadsFolders)
80
                ) {
81
                    $foldersToRemove->add($folder);
82
                }
83
            }
84
        } catch (\LogicException $e) {
85
            // Here we know that the download folder will exist.
86
        }
87
88
        return $foldersToRemove;
89
    }
90
91
    /**
92
     * Checks if a folder (or one of its parent, up to the $limit parameter) is found in the collection of folders.
93
     *
94
     * @param \SplFileInfo $folderToSearchFor
95
     * @param \App\Domain\Collection\FilesystemObjects $folders
96
     * @param bool $loopOverParentsFolders
97
     * @param \App\Domain\Collection\Path $untilPath
98
     *
99
     * @return bool
100
     * @throws \RuntimeException
101
     */
102
    private function isFolderInCollection(
103
        \SplFileInfo $folderToSearchFor,
104
        FilesystemObjects $folders,
105
        bool $loopOverParentsFolders = false,
106
        ?Path $untilPath = null
107
    ): bool {
108
        foreach ($folders as $folder) {
109
            do {
110
                // This allows to match "/root/path" in "/root/path" or "/root/path/sub_path"
111
                if (0 === strpos($folder->getRealPath(), $folderToSearchFor->getRealPath())) {
112
                    return true;
113
                }
114
115
                if (!$loopOverParentsFolders) {
116
                    break;
117
                }
118
                if (null === $untilPath) {
119
                    throw new \RuntimeException(
120
                        'If $loopOverParentsFolders is set to true, then $untilPath must be provided.'.
121
                        'Otherwise you will experience infinite loops.'
122
                    );
123
                }
124
125
                $folderToSearchFor = $folderToSearchFor->getPathInfo();
126
127
            } while ($folderToSearchFor->getRealPath() !== (string) $untilPath);
128
        }
129
130
        return false;
131
    }
132
133
    /**
134
     * @param \App\Domain\Collection\FilesystemObjects $foldersToRemove
135
     * @param \App\Domain\Collection\Path $downloadPath
136
     */
137
    private function removeFolders(FilesystemObjects $foldersToRemove, Path $downloadPath): void
0 ignored issues
show
Unused Code introduced by
The parameter $downloadPath is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
138
    {
139
        $errors = [];
140
        foreach ($foldersToRemove as $folderToRemove) {
141
            $relativeFolderPath = $folderToRemove->getRelativePathname();
142
143
            try {
144
                (new Filesystem())->remove($folderToRemove->getRealPath());
145
146
                $this->ui->writeln(
147
                    sprintf(
148
                        '%s* The folder <info>%s</info> has been removed.',
149
                        $this->ui->indent(2),
150
                        $relativeFolderPath
151
                    )
152
                );
153
            } catch (\Exception $e) {
154
                $this->ui->logError(
155
                    sprintf(
156
                        '%s* <error>The folder %s could not be removed.</error>',
157
                        $this->ui->indent(2),
158
                        $relativeFolderPath
159
                    ),
160
                    $errors
161
                );
162
            }
163
        }
164
        $this->ui->displayErrors($errors, 'the removal of folders', 'info', 1);
165
166
        $this->ui->writeln(PHP_EOL.'<info>Done.</info>'.PHP_EOL);
167
    }
168
169
    /**
170
     * @param \App\Platform\YouTube\Downloads $downloads
171
     *
172
     * @return \App\Domain\Collection\FilesystemObjects
173
     */
174
    private function getCompletedDownloadsFolders(Downloads $downloads): FilesystemObjects
175
    {
176
        $completedDownloadsFolders = new FilesystemObjects();
0 ignored issues
show
Bug introduced by
The call to FilesystemObjects::__construct() misses a required argument $elements.

This check looks for function calls that miss required arguments.

Loading history...
177
        foreach ($downloads as $download) {
178
            try {
179
                foreach ($this->getDownloadFolderFinder($download) as $downloadFolder) {
180
                    $completedDownloadsFolders->add($downloadFolder->getPathInfo());
181
                }
182
            } catch (\InvalidArgumentException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
183
            }
184
        }
185
186
        return $completedDownloadsFolders;
187
    }
188
189
    /**
190
     * @param \App\Domain\Collection\FilesystemObjects $foldersToRemove
191
     * @param \App\Domain\Collection\Path $downloadPath
192
     *
193
     * @return bool
194
     */
195
    private function shouldRemoveFolders(FilesystemObjects $foldersToRemove, Path $downloadPath): bool
196
    {
197
        $this->ui->write(
198
            sprintf(
199
                'Synchronize the <info>%s</info> folder with the downloaded contents... ',
200
                (string) $downloadPath
201
            )
202
        );
203
204
        if ($foldersToRemove->isEmpty()) {
205
            $this->ui->writeln('<info>Done.</info>');
206
207
            return false;
208
        }
209
210
        $this->ui->writeln(PHP_EOL);
211
212
        if (!$this->ui->isDryRun() && !$this->ui->isInteractive()) {
213
            return true;
214
        }
215
216
        $confirmationDefault = true;
217
218
        // If there's less than 10 folders, we can display them
219
        $nbFoldersToRemove = $foldersToRemove->count();
220
        if ($nbFoldersToRemove <= 10) {
221
            $this->ui->writeln(
222
                sprintf(
223
                    '%sThe script is about to remove the following folders from <info>%s</info>:',
224
                    $this->ui->indent(),
225
                    (string) $downloadPath
226
                )
227
            );
228
            $this->ui->listing(
229
                $foldersToRemove
230
                    ->map(function (\SplFileInfo $folder) use ($downloadPath) {
231
                        return sprintf(
232
                            '<info>%s</info>',
233
                            str_replace((string) $downloadPath.DIRECTORY_SEPARATOR, '', $folder->getRealPath())
234
                        );
235
                    })
236
                    ->toArray(),
237
                3
238
            );
239
        } else {
240
            $confirmationDefault = false;
241
242
            $this->ui->write(
243
                sprintf(
244
                    '%sThe script is about to remove <question> %s </question> folders from <info>%s</info>. ',
245
                    $this->ui->indent(),
246
                    $nbFoldersToRemove,
247
                    (string) $downloadPath
248
                )
249
            );
250
        }
251
252
        $this->ui->write($this->ui->indent());
253
254
        if ($this->skip() || !$this->ui->confirm($confirmationDefault)) {
255
            $this->ui->writeln(($this->ui->isDryRun() ? '' : PHP_EOL).'<info>Done.</info>'.PHP_EOL);
256
257
            return false;
258
        }
259
260
        return true;
261
    }
262
}
263