ScanStoragesCommand::getItemFromFile()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 0
cts 9
cp 0
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 5
nc 3
nop 2
crap 12
1
<?php
2
/**
3
 * AnimeDb package.
4
 *
5
 * @author    Peter Gribanov <[email protected]>
6
 * @copyright Copyright (c) 2011, Peter Gribanov
7
 * @license   http://opensource.org/licenses/GPL-3.0 GPL v3
8
 */
9
10
namespace AnimeDb\Bundle\CatalogBundle\Command;
11
12
use AnimeDb\Bundle\CatalogBundle\Console\Output\Decorator;
13
use AnimeDb\Bundle\CatalogBundle\Entity\Item;
14
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
15
use Symfony\Component\Console\Input\InputInterface;
16
use Symfony\Component\Console\Output\OutputInterface;
17
use Symfony\Component\Console\Input\InputArgument;
18
use Symfony\Component\Console\Input\InputOption;
19
use AnimeDb\Bundle\CatalogBundle\Entity\Storage;
20
use Symfony\Component\Finder\Finder;
21
use AnimeDb\Bundle\CatalogBundle\Event\Listener\Entity\Storage as StorageListener;
22
use AnimeDb\Bundle\CatalogBundle\Event\Storage\StoreEvents;
23
use AnimeDb\Bundle\CatalogBundle\Event\Storage\UpdateItemFiles;
24
use AnimeDb\Bundle\CatalogBundle\Event\Storage\DetectedNewFiles;
25
use AnimeDb\Bundle\CatalogBundle\Event\Storage\DeleteItemFiles;
26
use AnimeDb\Bundle\CatalogBundle\Repository\Storage as StorageRepository;
27
use Symfony\Component\Finder\SplFileInfo;
28
use AnimeDb\Bundle\CatalogBundle\Console\Output\LazyWrite;
29
use AnimeDb\Bundle\CatalogBundle\Console\Progress\Export;
30
use AnimeDb\Bundle\CatalogBundle\Console\Progress\PresetOutput;
31
use Symfony\Component\Console\Output\NullOutput;
32
use Patchwork\Utf8;
33
34
/**
35
 * Scan storages for new items.
36
 *
37
 * @author  Peter Gribanov <[email protected]>
38
 */
39
class ScanStoragesCommand extends ContainerAwareCommand
40
{
41
    /**
42
     * Allowable extension.
43
     *
44
     * @var array
45
     */
46
    protected $allow_ext = [
47
        'avi',
48
        'mkv',
49
        'm1v',
50
        'm2v',
51
        'm4v',
52
        'mov',
53
        'qt',
54
        'mpeg',
55
        'mpg',
56
        'mpe',
57
        'ogg',
58
        'rm',
59
        'wmv',
60
        'asf',
61
        'wm',
62
        'm2ts',
63
        'mts',
64
        'm2t',
65
        'mp4',
66
        'mov',
67
        '3gp',
68
        '3g2',
69
        'k3g',
70
        'mp2',
71
        'mpv2',
72
        'mod',
73
        'vob',
74
        'f4v',
75
        'ismv',
76
    ];
77
78
    protected function configure()
79
    {
80
        $this
81
            ->setName('animedb:scan-storage')
82
            ->setDescription('Scan storages for new items')
83
            ->addArgument(
84
                'storage',
85
                InputArgument::OPTIONAL,
86
                'Id scanned storage'
87
            )
88
            ->addOption(
89
                'force',
90
                'f',
91
                InputOption::VALUE_NONE,
92
                'Ignore the last modified storage'
93
            )
94
            ->addOption(
95
                'no-progress',
96
                null,
97
                InputOption::VALUE_NONE,
98
                'Disable progress bar'
99
            )
100
            ->addOption(
101
                'export',
102
                null,
103
                InputOption::VALUE_REQUIRED,
104
                'Export progress to file (disables progress as --no-progress)'
105
            )
106
            ->setHelp(<<<'EOT'
107
Example scan all storages:
108
109
<info>php app/console animedb:scan-storage</info>
110
111
Example scan storage with id <info>1</info>:
112
113
<info>php app/console animedb:scan-storage 1</info>
114
EOT
115
            );
116
    }
117
118
    /**
119
     * @param InputInterface $input
120
     * @param OutputInterface $output
121
     *
122
     * @return int
123
     */
124
    protected function execute(InputInterface $input, OutputInterface $output)
125
    {
126
        $start = time();
127
128
        $em = $this->getContainer()->get('doctrine.orm.entity_manager');
129
        $dispatcher = $this->getContainer()->get('event_dispatcher');
130
        /* @var $rep StorageRepository */
131
        $rep = $em->getRepository('AnimeDbCatalogBundle:Storage');
132
133
        $progress = $this->getProgress($input, $output);
134
        $lazywrite = new LazyWrite($output);
135
        $lazywrite->setLazyWrite(!$input->getOption('no-progress'));
136
137
        // get list storages
138
        if ($id = $input->getArgument('storage')) {
139
            $storage = $rep->find($id);
140
            if (!($storage instanceof Storage)) {
141
                throw new \InvalidArgumentException('Not found the storage with id: '.$id);
142
            }
143
            if (!$storage->isWritable()) {
144
                throw new \InvalidArgumentException('Storage "'.$storage->getName().'" can not be scanned');
145
            }
146
            $storages = [$storage];
147
        } else {
148
            $storages = $rep->getList(Storage::getTypesWritable());
149
        }
150
151
        /* @var $storage Storage */
152
        foreach ($storages as $storage) {
153
            $output->writeln('Scan storage <info>'.$storage->getName().'</info>:');
154
155
            $path = $storage->getPath();
156
            $path = Utf8::wrapPath($path); // wrap path for current fs
157
158
            if (!file_exists($path)) {
159
                $output->writeln('Storage is not available');
160
                continue;
161
            }
162
163
            // check storage id
164
            $owner = $this->checkStorageId($path, $storage, $rep);
165
            if ($owner instanceof Storage) {
166
                $output->writeln('Path <info>'.$storage->getPath().'</info> reserved storage <info>'
167
                    .$owner->getName().'</info>');
168
                continue;
169
            }
170
171
            // storage not modified
172
            if (!$input->getOption('force') &&
173
                $storage->getFileModified() &&
174
                filemtime($path) == $storage->getFileModified()->getTimestamp()
175
            ) {
176
                $output->writeln('Storage is not modified');
177
                continue;
178
            }
179
180
            $files = $this->getFilesByPath($path);
181
            $total = $files->count();
182
            // total files +1% for check of delete files
183
            $progress->start(ceil($total + ($total * 0.01)));
184
            $progress->display();
185
186
            /* @var $file SplFileInfo */
187
            foreach ($files as $file) {
188
                // ignore not supported files
189
                if ($file->isFile() && !$this->isAllowFile($file)) {
190
                    $progress->advance();
191
                    continue;
192
                }
193
194
                // item is exists and modified
195
                if ($item = $this->getItemFromFile($storage, $file)) {
196
                    if ($item->getDateUpdate()->getTimestamp() < $file->getPathInfo()->getMTime()) {
197
                        $dispatcher->dispatch(StoreEvents::UPDATE_ITEM_FILES, new UpdateItemFiles($item));
0 ignored issues
show
Bug introduced by
It seems like $item defined by $this->getItemFromFile($storage, $file) on line 195 can also be of type boolean; however, AnimeDb\Bundle\CatalogBu...temFiles::__construct() does only seem to accept object<AnimeDb\Bundle\CatalogBundle\Entity\Item>, 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...
198
                        $lazywrite->writeln('Changes are detected in files of item <info>'.$item->getName().'</info>');
199
                    }
200
                } else {
201
                    // remove wrap prefix
202
                    list(, $file) = explode('://', $file->getPathname(), 2);
203
                    $file = new SplFileInfo($file, '', '');
204
205
                    // it is a new item
206
                    $name = $this->getContainer()->get('anime_db.storage.filename_cleaner')->clean($file);
207
                    $dispatcher->dispatch(
208
                        StoreEvents::DETECTED_NEW_FILES,
209
                        new DetectedNewFiles($storage, $file, $name)
210
                    );
211
                    $lazywrite->writeln('Detected files for new item <info>'.$file->getFilename().'</info>');
212
                }
213
                $progress->advance();
214
            }
215
            $em->refresh($storage);
216
217
            // check of delete file for item
218
            foreach ($this->getItemsOfDeletedFiles($storage, $files) as $item) {
219
                $dispatcher->dispatch(StoreEvents::DELETE_ITEM_FILES, new DeleteItemFiles($item));
220
                $lazywrite->writeln('<error>Files for item "'.$item->getName().'" is not found</error>');
221
            }
222
            $progress->advance();
223
            $progress->finish();
224
            $lazywrite->writeAll();
225
226
            // update date modified
227
            $storage->setFileModified(new \DateTime(date('Y-m-d H:i:s', filemtime($path))));
228
            $em->persist($storage);
229
            $output->writeln('');
230
        }
231
        $em->flush();
232
233
        $output->writeln('Time: <info>'.(time() - $start).'</info> s.');
234
    }
235
236
    /**
237
     * Get items of deleted files.
238
     *
239
     * @param Storage $storage
240
     * @param Finder $finder
241
     *
242
     * @return array
243
     */
244
    protected function getItemsOfDeletedFiles(Storage $storage, Finder $finder)
245
    {
246
        $items = [];
247
        // check of delete file for item
248
        foreach ($storage->getItems() as $item) {
249
            foreach ($finder as $file) {
250
                if (pathinfo($item->getPath(), PATHINFO_BASENAME) == $file->getFilename()) {
251
                    continue 2;
252
                }
253
            }
254
            $items[] = $item;
255
        }
256
257
        return $items;
258
    }
259
260
    /**
261
     * Get item from files.
262
     *
263
     * @param Storage $storage
264
     * @param SplFileInfo $file
265
     *
266
     * @return Item|bool
267
     */
268
    protected function getItemFromFile(Storage $storage, SplFileInfo $file)
269
    {
270
        /* @var $item Item */
271
        foreach ($storage->getItems() as $item) {
272
            if (pathinfo($item->getPath(), PATHINFO_BASENAME) == $file->getFilename()) {
273
                return $item;
274
            }
275
        }
276
277
        return false;
278
    }
279
280
    /**
281
     * Get files by path.
282
     *
283
     * @param string $path
284
     *
285
     * @return Finder
286
     */
287
    protected function getFilesByPath($path)
288
    {
289
        return Finder::create()
290
            ->in($path)
291
            ->ignoreUnreadableDirs()
292
            ->depth('== 0')
293
            ->notName('.*');
294
    }
295
296
    /**
297
     * Is allow file.
298
     *
299
     * @param SplFileInfo $file
300
     *
301
     * @return bool
302
     */
303
    protected function isAllowFile(SplFileInfo $file)
304
    {
305
        return in_array(strtolower(pathinfo($file->getFilename(), PATHINFO_EXTENSION)), $this->allow_ext);
306
    }
307
308
    /**
309
     * Update storage id.
310
     *
311
     * @param string $path
312
     * @param Storage $storage
313
     * @param StorageRepository $rep
314
     *
315
     * @return Storage|bool
316
     */
317
    protected function checkStorageId($path, Storage $storage, StorageRepository $rep)
318
    {
319
        if (!file_exists($path.StorageListener::ID_FILE)) {
320
            // path is free. reserve for me
321
            file_put_contents($path.StorageListener::ID_FILE, $storage->getId());
322
        } elseif (file_get_contents($path.StorageListener::ID_FILE) == $storage->getId()) {
323
            // it is my path. do nothing
324
        } elseif (!($duplicate = $rep->find(file_get_contents($path.StorageListener::ID_FILE)))) {
325
            // this path is reserved storage that was removed and now this path is free
326
            file_put_contents($path.StorageListener::ID_FILE, $storage->getId());
327
        } else {
328
            return $duplicate;
329
        }
330
331
        return true;
332
    }
333
334
    /**
335
     * Get progress.
336
     *
337
     * @param InputInterface $input
338
     * @param OutputInterface $output
339
     *
340
     * @return Decorator
341
     */
342
    protected function getProgress(InputInterface $input, OutputInterface $output)
343
    {
344
        if ($input->getOption('no-progress')) {
345
            $output = new NullOutput();
346
        }
347
348
        // export progress only for one storage
349
        if (!$input->getArgument('storage')) {
350
            $input->setOption('export', null);
351
        }
352
353
        if ($export_file = $input->getOption('export')) {
354
            // progress is redirected to the export file
355
            $input->setOption('no-progress', true);
356
            $progress = new Export($this->getHelperSet()->get('progress'), new NullOutput(), $export_file);
357
        } else {
358
            $progress = new PresetOutput($this->getHelperSet()->get('progress'), $output);
359
        }
360
361
        $progress->setBarCharacter('<comment>=</comment>');
362
363
        return $progress;
364
    }
365
}
366