Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend management command by option to delete orphans #614

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
51 changes: 49 additions & 2 deletions easy_thumbnails/management/commands/thumbnail_cleanup.py
Expand Up @@ -18,6 +18,7 @@ class ThumbnailCollectionCleaner:
sources = 0
thumbnails = 0
thumbnails_deleted = 0
orphans_deleted = 0
source_refs_deleted = 0
execution_time = 0

Expand All @@ -41,19 +42,40 @@ def _check_if_exists(self, storage, path):
def _delete_sources_by_id(self, ids):
Source.objects.all().filter(id__in=ids).delete()

def _get_all_thumbfiles_in_storage(self, storage, path=None):
if path is None:
path = os.path.join(settings.MEDIA_ROOT, settings.THUMBNAIL_BASEDIR)

directories, files = storage.listdir(path)
for file in files:
yield os.path.join(path, file)

for dir in directories:
for file in self._get_all_thumbfiles_in_storage(storage, path=os.path.join(path, dir)):
yield file

def clean_up(self, dry_run=False, verbosity=1, last_n_days=0,
cleanup_path=None, storage=None):
cleanup_path=None, storage=None, delete_orphans=False):
"""
Iterate through sources. Delete database references to sources
not existing, including its corresponding thumbnails (files and
database references).
"""
if (last_n_days > 0 or cleanup_path) and delete_orphans:
self.stdout.write("WARNING! Conflicting options: "
"--delete-orphans not implemented with --last-n-days or --path.")
delete_orphans = False

if dry_run:
self.stdout.write("Dry run...")

if not storage:
storage = get_storage_class(settings.THUMBNAIL_DEFAULT_STORAGE)()

thumbfiles_in_storage = set()
if delete_orphans:
thumbfiles_in_storage = set(self._get_all_thumbfiles_in_storage(storage))

sources_to_delete = []
time_start = time.time()

Expand Down Expand Up @@ -84,13 +106,29 @@ def clean_up(self, dry_run=False, verbosity=1, last_n_days=0,
storage.delete(abs_thumbnail_path)
if verbosity > 0:
self.stdout.write("Deleting thumbnail: {}".format(abs_thumbnail_path))
elif delete_orphans:
for thumb in source.thumbnails.all():
abs_thumbnail_path = self._get_absolute_path(thumb.name)
try:
thumbfiles_in_storage.remove(abs_thumbnail_path)
except KeyError:
# thumbfile has just not been generated yet, so, ignore this exception
pass

if len(sources_to_delete) >= 1000 and not dry_run:
self._delete_sources_by_id(sources_to_delete)
sources_to_delete = []

if not dry_run:
self._delete_sources_by_id(sources_to_delete)

for abs_orphan_path in thumbfiles_in_storage:
self.orphans_deleted += 1
if not dry_run:
storage.delete(abs_orphan_path)
if verbosity > 0:
self.stdout.write("Deleting orphan: {}".format(abs_orphan_path))

self.execution_time = round(time.time() - time_start)

def print_stats(self):
Expand All @@ -104,6 +142,8 @@ def print_stats(self):
"Source references deleted from DB:", self.source_refs_deleted))
self.stdout.write("{0:<40} {1:>7}".format("Thumbnails deleted from disk:",
self.thumbnails_deleted))
self.stdout.write("{0:<40} {1:>7}".format("Orphans deleted from disk:",
self.orphans_deleted))
self.stdout.write("(Completed in {} seconds)\n".format(self.execution_time))


Expand Down Expand Up @@ -146,12 +186,19 @@ def add_arguments(self, parser):
dest='cleanup_path',
type=str,
help='Specify a path to clean up.')
parser.add_argument(
'--delete-orphans',
action='store_true',
dest='delete_orphans',
default=False,
help='Check for files in storage that have no source and delete them.')

def handle(self, *args, **options):
tcc = ThumbnailCollectionCleaner(self.stdout, self.stderr)
tcc.clean_up(
dry_run=options.get('dry_run', False),
verbosity=int(options.get('verbosity', 1)),
last_n_days=int(options.get('last_n_days', 0)),
cleanup_path=options.get('cleanup_path'))
cleanup_path=options.get('cleanup_path'),
delete_orphans=options.get('delete_orphans'))
tcc.print_stats()