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.
*