From 87527dfdc612c301134351b8b446eb9f9bb2d1c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 27 Aug 2019 18:54:02 +0200 Subject: [PATCH] Added an command which deletes all abandoned files created by attachments that dont exist anymore. --- src/Command/CleanAttachmentsCommand.php | 113 +++++++++++++++++++++++ src/Services/AttachmentHelper.php | 25 ++++- src/Services/AttachmentReverseSearch.php | 69 ++++++++++++++ 3 files changed, 203 insertions(+), 4 deletions(-) create mode 100644 src/Command/CleanAttachmentsCommand.php create mode 100644 src/Services/AttachmentReverseSearch.php diff --git a/src/Command/CleanAttachmentsCommand.php b/src/Command/CleanAttachmentsCommand.php new file mode 100644 index 00000000..5cf49443 --- /dev/null +++ b/src/Command/CleanAttachmentsCommand.php @@ -0,0 +1,113 @@ +attachment_helper = $attachmentHelper; + $this->reverseSearch = $reverseSearch; + $this->mimeTypeGuesser = new MimeTypes(); + parent::__construct(); + } + + protected function configure() + { + $this + ->setDescription('Lists (and deletes if wanted) attachments files that are not used anymore (abandoned files).') + ->setHelp('This command allows to find all files in the media folder which are not associated with an attachment anymore.'. + ' These files are not needed and can eventually deleted.'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + $mediaPath = $this->attachment_helper->getMediaPath(); + $io->note("The media path is " . $mediaPath); + + $finder = new Finder(); + //We look for files in the media folder only + $finder->files()->in($mediaPath); + $fs = new Filesystem(); + + $file_list = array(); + + $table = new Table($output); + $table->setHeaders(['Filename', 'MIME Type', 'Last modified date']); + $dateformatter = \IntlDateFormatter::create(null, \IntlDateFormatter::SHORT, \IntlDateFormatter::SHORT); + + foreach ($finder as $file) + { + //If not attachment object uses this file, print it + if (count($this->reverseSearch->findAttachmentsByFile($file)) == 0) { + $file_list[] = $file; + $table->addRow([ + $fs->makePathRelative($file->getPathname(), $mediaPath), + $this->mimeTypeGuesser->guessMimeType($file->getPathname()), + $dateformatter->format($file->getMTime()) + ]); + } + } + + if (count($file_list) > 0) { + $table->render(); + + $continue = $io->confirm(sprintf("Found %d abandoned files. Do you want to delete them? This can not be undone!", count($file_list)), false); + + if (!$continue) { + //We are finished here, when no files should be deleted + return; + } + + //Delete the files + $fs->remove($file_list); + //Delete empty folders: + $this->removeEmptySubFolders($mediaPath); + + $io->success("All abandoned files were removed."); + + } else { + $io->success("No abandoned files found."); + } + + + } + + /** + * This function removes all empty folders inside $path. Taken from https://stackoverflow.com/a/1833681 + * @param string $path The path in which the empty folders should be deleted + * @return bool + */ + protected function removeEmptySubFolders($path) + { + $empty=true; + foreach (glob($path . DIRECTORY_SEPARATOR . "*") as $file) + { + $empty &= is_dir($file) && $this->removeEmptySubFolders($file); + } + return $empty && rmdir($path); + } +} diff --git a/src/Services/AttachmentHelper.php b/src/Services/AttachmentHelper.php index a5d82582..3530da19 100644 --- a/src/Services/AttachmentHelper.php +++ b/src/Services/AttachmentHelper.php @@ -34,6 +34,7 @@ namespace App\Services; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\PartAttachment; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpFoundation\File\File; @@ -61,12 +62,21 @@ class AttachmentHelper } } + /** + * Returns the absolute path to the folder where all attachments are saved. + * @return string + */ + public function getMediaPath() : string + { + return $this->base_path; + } + /** * Converts an relative placeholder filepath (with %MEDIA% or older %BASE%) to an absolute filepath on disk. * @param string $placeholder_path The filepath with placeholder for which the real path should be determined. * @return string The absolute real path of the file */ - protected function placeholderToRealPath(string $placeholder_path) : string + public function placeholderToRealPath(string $placeholder_path) : string { //The new attachments use %MEDIA% as placeholders, which is the directory set in media_directory $placeholder_path = str_replace("%MEDIA%", $this->base_path, $placeholder_path); @@ -74,6 +84,9 @@ class AttachmentHelper //Older path entries are given via %BASE% which was the project root $placeholder_path = str_replace("%BASE%/data/media", $this->base_path, $placeholder_path); + //Normalize path + $placeholder_path = str_replace('\\', '/', $placeholder_path); + return $placeholder_path; } @@ -84,13 +97,17 @@ class AttachmentHelper * media directory. If set to true, the old version with %BASE% will be used, which is the project directory. * @return string The placeholder version of the filepath */ - protected function realPathToPlaceholder(string $real_path, bool $old_version = false) : string + public function realPathToPlaceholder(string $real_path, bool $old_version = false) : string { if ($old_version) { - return str_replace($this->base_path, "%BASE%/data/media", $real_path); + $real_path = str_replace($this->base_path, "%BASE%/data/media", $real_path); + } else { + $real_path = str_replace($this->base_path, "%MEDIA%", $real_path); } - return str_replace($this->base_path, "%MEDIA%", $real_path); + //Normalize path + $real_path = str_replace('\\', '/', $real_path); + return $real_path; } /** diff --git a/src/Services/AttachmentReverseSearch.php b/src/Services/AttachmentReverseSearch.php new file mode 100644 index 00000000..3d69d303 --- /dev/null +++ b/src/Services/AttachmentReverseSearch.php @@ -0,0 +1,69 @@ +em = $em; + $this->attachment_helper = $attachmentHelper; + } + + /** + * Find all attachments that use the given file + * @param File $file + * @return Attachment[] + */ + public function findAttachmentsByFile(\SplFileInfo $file) : array + { + //Path with %MEDIA% + $relative_path_new = $this->attachment_helper->realPathToPlaceholder($file->getPathname()); + //Path with %BASE% + $relative_path_old = $this->attachment_helper->realPathToPlaceholder($file->getPathname(), true); + + $repo = $this->em->getRepository(Attachment::class); + return $repo->findBy(['path' => [$relative_path_new, $relative_path_old]]); + } + +} \ No newline at end of file