bgneal@665: """dlorphan - a management command to remove orphaned downloads files."""
bgneal@665: 
bgneal@665: from optparse import make_option
bgneal@665: import os
bgneal@665: 
bgneal@665: from django.core.management.base import NoArgsCommand
bgneal@665: from django.conf import settings
bgneal@665: 
bgneal@665: from downloads.models import Download, PendingDownload
bgneal@665: 
bgneal@665: 
bgneal@665: class Command(NoArgsCommand):
bgneal@665:     help = "Finds and optionally deletes orphan downloads files"
bgneal@665:     option_list = NoArgsCommand.option_list + (
bgneal@665:             make_option('--delete',
bgneal@665:                 action='store_true',
bgneal@665:                 dest='delete',
bgneal@665:                 default=False,
bgneal@665:                 help='Delete orphan files'),
bgneal@665:     )
bgneal@665: 
bgneal@665:     def handle_noargs(self, **options):
bgneal@665:         """Find and optionally delete orphan downloads files."""
bgneal@665: 
bgneal@665:         delete = options.get('delete', False)
bgneal@665: 
bgneal@665:         orphans = set()
bgneal@665:         missing_pending = []
bgneal@665:         missing_dls = []
bgneal@665: 
bgneal@665:         dls_dir = os.path.join(settings.MEDIA_ROOT, 'downloads')
bgneal@665:         # find the set of all files in the downloads area
bgneal@665:         for root, dirs, files in os.walk(dls_dir, followlinks=True):
bgneal@665:             for name in files:
bgneal@665:                 orphans.add(unicode(os.path.join(root, name), 'utf-8'))
bgneal@665: 
bgneal@665:         # examine the pending downloads:
bgneal@665:         for dl in PendingDownload.objects.iterator():
bgneal@665:             try:
bgneal@665:                 orphans.remove(dl.file.path)
bgneal@665:             except KeyError:
bgneal@665:                 missing_pending.append(dl)
bgneal@665: 
bgneal@665:         # examine the downloads:
bgneal@665:         for dl in Download.objects.iterator():
bgneal@665:             try:
bgneal@665:                 orphans.remove(dl.file.path)
bgneal@665:             except KeyError:
bgneal@665:                 missing_dls.append(dl)
bgneal@665: 
bgneal@667:         if orphans and delete:
bgneal@667:             for path in orphans:
bgneal@667:                 self.stdout.write("Deleting: {}\n".format(path))
bgneal@667:                 os.remove(path)
bgneal@667:         elif orphans:
bgneal@667:             self.stdout.write("Orphan files:\n")
bgneal@665:             for orphan in orphans:
bgneal@667:                 self.stdout.write("{}\n".format(orphan))
bgneal@665: 
bgneal@665:         if missing_pending:
bgneal@667:             self.stdout.write("PendingDownloads with missing files:\n")
bgneal@665:             for dl in missing_pending:
bgneal@667:                 self.stdout.write("{}\n".format(dl))
bgneal@665: 
bgneal@665:         if missing_dls:
bgneal@667:             self.stdout.write("Downloads with missing files:\n")
bgneal@665:             for dl in missing_dls:
bgneal@667:                 self.stdout.write("{}\n".format(dl))
bgneal@665: 
bgneal@667:         empty_dirs = []
bgneal@667:         # check for empty directories after deletions
bgneal@667:         for root, dirs, files in os.walk(dls_dir, followlinks=True):
bgneal@667:             if not len(dirs) and not len(files):
bgneal@667:                 empty_dirs.append(root)
bgneal@665: 
bgneal@667:         if empty_dirs and delete:
bgneal@665:             for path in empty_dirs:
bgneal@667:                 self.stdout.write("Deleting empty dir: {}\n".format(path))
bgneal@665:                 os.removedirs(path)
bgneal@667:         elif empty_dirs:
bgneal@667:             self.stdout.write("Empty directories:\n")
bgneal@667:             for d in empty_dirs:
bgneal@667:                 self.stdout.write("{}\n".format(d))