diff --git a/src/Wallabag/CoreBundle/Command/CleanDownloadedImagesCommand.php b/src/Wallabag/CoreBundle/Command/CleanDownloadedImagesCommand.php new file mode 100644 index 000000000..bb3946210 --- /dev/null +++ b/src/Wallabag/CoreBundle/Command/CleanDownloadedImagesCommand.php @@ -0,0 +1,123 @@ +setName('wallabag:clean-downloaded-images') + ->setDescription('Cleans downloaded images which are no more associated to an entry') + ->addArgument( + 'username', + InputArgument::OPTIONAL, + 'User to clean' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->io = new SymfonyStyle($input, $output); + + $username = $input->getArgument('username'); + + if ($username) { + try { + $user = $this->getContainer()->get('wallabag_user.user_repository')->findOneByUserName($username); + $this->clean($user); + } catch (NoResultException $e) { + $this->io->error(sprintf('User "%s" not found.', $username)); + + return 1; + } + + $this->io->success('Finished cleaning.'); + } else { + $users = $this->getContainer()->get('wallabag_user.user_repository')->findAll(); + + $this->io->text(sprintf('Cleaning through %d user accounts', \count($users))); + + foreach ($users as $user) { + $this->clean($user); + } + $this->io->success(sprintf('Finished cleaning. %d deleted images', $this->deleted)); + } + + return 0; + } + + private function clean(User $user) + { + $this->io->text(sprintf('Processing user %s', $user->getUsername())); + + $repo = $this->getContainer()->get('wallabag_core.entry_repository'); + $downloadImages = $this->getContainer()->get('wallabag_core.entry.download_images'); + $baseFolder = $downloadImages->getBaseFolder(); + + $entries = $repo->findAllEntriesIdByUserId($user->getId()); + + $deletedCount = 0; + + // first retrieve _valid_ folders from existing entries + $hashToId = []; + $validPaths = []; + foreach ($entries as $entry) { + $path = $downloadImages->getRelativePath($entry['id']); + + if (!file_exists($baseFolder . '/' . $path)) { + continue; + } + + // only store the hash, not the full path + $hash = explode('/', $path)[2]; + $validPaths[] = $hash; + $hashToId[$hash] = $entry['id']; + } + + // then retrieve _existing_ folders in the image folder + $finder = new Finder(); + $finder + ->directories() + ->ignoreDotFiles(true) + ->depth(2) + ->in($baseFolder); + + $existingPaths = []; + foreach ($finder as $file) { + $existingPaths[] = $file->getFilename(); + } + + // check if existing path are valid, if not, remove all images and the folder + foreach ($existingPaths as $existingPath) { + if (!\in_array($existingPath, $validPaths, true)) { + $fullPath = $baseFolder . '/' . $existingPath[0] . '/' . $existingPath[1] . '/' . $existingPath; + $res = array_map('unlink', glob($fullPath . '/*.*')); + + rmdir($fullPath); + + $deletedCount += \count($res); + + $this->io->text(sprintf('Deleted images in %s: %d', $existingPath, \count($res))); + } + } + + $this->deleted += $deletedCount; + + $this->io->text(sprintf('Deleted %d images for user %s', $deletedCount, $user->getUserName())); + } +} diff --git a/src/Wallabag/CoreBundle/Helper/DownloadImages.php b/src/Wallabag/CoreBundle/Helper/DownloadImages.php index 1d98fd1a9..601b4f314 100644 --- a/src/Wallabag/CoreBundle/Helper/DownloadImages.php +++ b/src/Wallabag/CoreBundle/Helper/DownloadImages.php @@ -37,6 +37,11 @@ class DownloadImages $this->setFolder(); } + public function getBaseFolder() + { + return $this->baseFolder; + } + /** * Process the html and extract images URLs from it. * @@ -210,6 +215,29 @@ class DownloadImages @rmdir($folderPath); } + /** + * Generate the folder where we are going to save images based on the entry url. + * + * @param int $entryId ID of the entry + * @param bool $createFolder Should we create the folder for the given id? + * + * @return string + */ + public function getRelativePath($entryId, $createFolder = true) + { + $hashId = hash('crc32', $entryId); + $relativePath = $hashId[0] . '/' . $hashId[1] . '/' . $hashId; + $folderPath = $this->baseFolder . '/' . $relativePath; + + if (!file_exists($folderPath) && $createFolder) { + mkdir($folderPath, 0777, true); + } + + $this->logger->debug('DownloadImages: Folder used for that Entry id', ['folder' => $folderPath, 'entryId' => $entryId]); + + return $relativePath; + } + /** * Get images urls from the srcset image attribute. * @@ -254,28 +282,6 @@ class DownloadImages } } - /** - * Generate the folder where we are going to save images based on the entry url. - * - * @param int $entryId ID of the entry - * - * @return string - */ - private function getRelativePath($entryId) - { - $hashId = hash('crc32', $entryId); - $relativePath = $hashId[0] . '/' . $hashId[1] . '/' . $hashId; - $folderPath = $this->baseFolder . '/' . $relativePath; - - if (!file_exists($folderPath)) { - mkdir($folderPath, 0777, true); - } - - $this->logger->debug('DownloadImages: Folder used for that Entry id', ['folder' => $folderPath, 'entryId' => $entryId]); - - return $relativePath; - } - /** * Make an $url absolute based on the $base. *