Completed
Push — master ( 738ba0...8b90c1 )
by Dorian
01:21
created

FilesystemManager::cleanFilesystem()   C

Complexity

Conditions 9
Paths 154

Size

Total Lines 52
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 52
rs 6.0761
c 0
b 0
f 0
cc 9
eloc 29
nc 154
nop 2

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
2
3
namespace App\Platform\YouTube;
4
5
use App\Domain\Collection;
6
use App\Domain\Path;
7
use App\Domain\PathPart;
8
use Symfony\Component\Filesystem\Filesystem;
9
use Symfony\Component\Finder\Finder;
10
11
trait FilesystemManager
12
{
13
    use DryRunner;
14
15
    /** @var array */
16
    private $options;
17
18
    /**
19
     * @param \App\Platform\YouTube\Download $download
20
     *
21
     * @return \Symfony\Component\Finder\Finder|\SplFileInfo[]
22
     * @throws \InvalidArgumentException
23
     */
24
    private function getDownloadFolderFinder(Download $download): Finder
25
    {
26
        $placeholders = [
27
            '%video_id%' => $download->getVideoId(),
28
            '%file_extension%' => $download->getFileExtension(),
29
        ];
30
31
        $downloadPathPart = new PathPart([
32
            'path' => (string) $download->getPath(),
33
            'priority' => 0,
34
        ]);
35
        $folderPathPart = new PathPart([
36
            'path' => $this->options['patterns']['folder'],
37
            'priority' => 1,
38
            'substitutions' => $placeholders,
39
        ]);
40
41
        return (new Finder())
42
            ->files()
43
            ->depth('== 0')
44
            ->in((string) new Path([$downloadPathPart, $folderPathPart]))
45
            ->name(
46
                str_replace(
47
                    array_keys($placeholders),
48
                    array_values($placeholders),
49
                    $this->options['patterns']['filename']
50
                )
51
            );
52
    }
53
54
    /**
55
     * {@inheritdoc}
56
     * @param \App\Platform\YouTube\Download[]|\App\Domain\Collection $downloads
57
     *
58
     * @throws \RuntimeException
59
     */
60
    private function cleanFilesystem(Collection $downloads, Path $downloadPath): void
61
    {
62
        $this->ui->write(
63
            sprintf(
64
                'Synchronize the <info>%s</info> folder with the downloaded contents... ',
65
                (string) $downloadPath
66
            )
67
        );
68
69
        $completedDownloadsFolders = new Collection();
70
        foreach ($downloads as $download) {
71
            try {
72
                foreach ($this->getDownloadFolderFinder($download) as $downloadFolder) {
73
                    $parentFolder = $downloadFolder->getPathInfo();
74
75
                    // Using a key ensures there's no duplicates
76
                    $completedDownloadsFolders->set($parentFolder->getRealPath(), $parentFolder);
77
                }
78
            } catch (\InvalidArgumentException $e) {
79
            }
80
        }
81
82
        /** @var \SplFileInfo[]|\App\Domain\Collection $foldersToRemove */
83
        $foldersToRemove = new Collection();
84
        try {
85
            $allFolders = (new Finder())
86
                ->directories()
87
                ->in((string) $downloadPath)
88
                ->sort(function (\SplFileInfo $fileInfoA, \SplFileInfo $fileInfoB) {
89
                    // Sort the result by folder depth
90
                    $a = substr_count($fileInfoA->getRealPath(), DIRECTORY_SEPARATOR);
91
                    $b = substr_count($fileInfoB->getRealPath(), DIRECTORY_SEPARATOR);
92
93
                    return $a <=> $b;
94
                });
95
96
            foreach ($allFolders->getIterator() as $folder) {
97
                if (!$this->isFolderInCollection($folder, $foldersToRemove, true, $downloadPath) &&
0 ignored issues
show
Bug introduced by
It seems like $foldersToRemove can also be of type array<integer,object<SplFileInfo>>; however, App\Platform\YouTube\Fil...:isFolderInCollection() does only seem to accept object<App\Domain\Collection>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
98
                    !$this->isFolderInCollection($folder, $completedDownloadsFolders)
99
                ) {
100
                    $foldersToRemove->add($folder);
101
                }
102
            }
103
        } catch (\LogicException $e) {
104
            // Here we know that the download folder will exist.
105
        }
106
107
        $hasRemovedFolders = $this->removeFolders($foldersToRemove, $downloadPath);
0 ignored issues
show
Bug introduced by
It seems like $foldersToRemove can also be of type array<integer,object<SplFileInfo>>; however, App\Platform\YouTube\Fil...anager::removeFolders() does only seem to accept object<App\Domain\Collection>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
108
109
        $newLine = $hasRemovedFolders ? PHP_EOL : '';
110
        $this->ui->writeln($newLine.'<info>Done.</info>'.$newLine);
111
    }
112
113
    /**
114
     * Checks if a folder (or one of its parent, up to the $limit parameter) is found in the collection of folders.
115
     *
116
     * @param \SplFileInfo $folderToSearchFor
117
     * @param \SplFileInfo[]|\App\Domain\Collection $folders
118
     * @param bool $loopOverParentsFolders
119
     * @param Path $untilPath
120
     *
121
     * @return bool
122
     * @throws \RuntimeException
123
     */
124
    private function isFolderInCollection(
125
        \SplFileInfo $folderToSearchFor,
126
        Collection $folders,
127
        bool $loopOverParentsFolders = false,
128
        ?Path $untilPath = null
129
    ): bool {
130
        foreach ($folders as $folder) {
131
            do {
132
                // This allows to match "/root/path" in "/root/path" or "/root/path/sub_path"
133
                if (0 === strpos($folder->getRealPath(), $folderToSearchFor->getRealPath())) {
134
                    return true;
135
                }
136
137
                if (!$loopOverParentsFolders) {
138
                    break;
139
                }
140
                if (null === $untilPath) {
141
                    throw new \RuntimeException(
142
                        'If $loopOverParentsFolders is set to true, then $untilPath must be provided.'.
143
                        'Otherwise you will experience infinite loops.'
144
                    );
145
                }
146
147
                $folderToSearchFor = $folderToSearchFor->getPathInfo();
148
149
            } while ($folderToSearchFor->getRealPath() !== (string) $untilPath);
150
        }
151
152
        return false;
153
    }
154
155
    /**
156
     * @param \SplFileInfo[]|\App\Domain\Collection $foldersToRemove
157
     * @param \App\Domain\Path $downloadPath
158
     *
159
     * @return bool Whether folders were removed or not.
160
     */
161
    private function removeFolders(Collection $foldersToRemove, Path $downloadPath): bool
162
    {
163
        $nbFoldersToRemove = $foldersToRemove->count();
164
        if (empty($nbFoldersToRemove)) {
165
            return false;
166
        }
167
168
        $this->ui->writeln(PHP_EOL);
169
170
        $confirmationDefault = true;
171
172
        // If there's less than 10 folders, we can display them
173
        if ($nbFoldersToRemove <= 10) {
174
            $this->ui->writeln(
175
                sprintf(
176
                    '%sThe script is about to remove the following folders from <info>%s</info>:',
177
                    $this->ui->indent(),
178
                    (string) $downloadPath
179
                )
180
            );
181
            $this->ui->listing(
182
                $foldersToRemove
183
                    ->map(function (\SplFileInfo $folder) use ($downloadPath) {
184
                        return sprintf(
185
                            '<info>%s</info>',
186
                            str_replace((string) $downloadPath.DIRECTORY_SEPARATOR, '', $folder->getRealPath())
187
                        );
188
                    })
189
                    ->toArray(),
190
                3
191
            );
192
        } else {
193
            $confirmationDefault = false;
194
195
            $this->ui->write(
196
                sprintf(
197
                    '%sThe script is about to remove <question> %s </question> folders from <info>%s</info>. ',
198
                    $this->ui->indent(),
199
                    $nbFoldersToRemove,
200
                    (string) $downloadPath
201
                )
202
            );
203
        }
204
205
        $foldersWereRemoved = false;
206
207
        $this->ui->write($this->ui->indent());
208
        if ($this->skip() || !$this->ui->confirm($confirmationDefault)) {
209
            return $foldersWereRemoved;
210
        }
211
212
        $errors = [];
213
        foreach ($foldersToRemove as $folderToRemove) {
214
            $relativeFolderPath = $folderToRemove->getRelativePathname();
215
216
            try {
217
                (new Filesystem())->remove($folderToRemove->getRealPath());
218
219
                $foldersWereRemoved = true;
220
221
                $this->ui->writeln(
222
                    sprintf(
223
                        '%s* The folder <info>%s</info> has been removed.',
224
                        $this->ui->indent(2),
225
                        $relativeFolderPath
226
                    )
227
                );
228
            } catch (\Exception $e) {
229
                $this->ui->logError(
230
                    sprintf(
231
                        '%s* <error>The folder %s could not be removed.</error>',
232
                        $this->ui->indent(2),
233
                        $relativeFolderPath
234
                    ),
235
                    $errors
236
                );
237
            }
238
        }
239
        $this->ui->displayErrors($errors, 'the removal of folders', 'info', 1);
240
241
        return $foldersWereRemoved;
242
    }
243
}
244