from datetime import timedelta import pytz from django.db.models import F from django.template.defaultfilters import filesizeformat from django.utils.timezone import now from main.models import PackageFile from packages.models import Depend, PackageRelation from .models import DeveloperKey class DeveloperReport(object): def __init__(self, slug, name, desc, packages_func, names=None, attrs=None, personal=True): self.slug = slug self.name = name self.description = desc self.packages = packages_func self.names = names self.attrs = attrs self.personal = personal def old(packages): cutoff = now() - timedelta(days=365 * 2) return packages.filter(build_date__lt=cutoff).order_by('build_date') def outofdate(packages): cutoff = now() - timedelta(days=30) return packages.filter(flag_date__lt=cutoff).order_by('flag_date') def big(packages): cutoff = 50 * 1024 * 1024 packages = packages.filter( compressed_size__gte=cutoff).order_by('-compressed_size') # Format the compressed and installed sizes with MB/GB/etc suffixes for package in packages: package.compressed_size_pretty = filesizeformat( package.compressed_size) package.installed_size_pretty = filesizeformat(package.installed_size) return packages def badcompression(packages): cutoff = 0.90 * F('installed_size') packages = packages.filter( compressed_size__gt=25 * 1024, installed_size__gt=25 * 1024, compressed_size__gte=cutoff).order_by('-compressed_size') # Format the compressed and installed sizes with MB/GB/etc suffixes for package in packages: package.compressed_size_pretty = filesizeformat( package.compressed_size) package.installed_size_pretty = filesizeformat(package.installed_size) ratio = package.compressed_size / float(package.installed_size) package.ratio = '%.3f' % ratio package.compress_type = package.filename.split('.')[-1] return packages def uncompressed_man(packages, username): # checking for all '.0'...'.9' + '.n' extensions bad_files = PackageFile.objects.filter( is_directory=False, directory__contains='/man/', filename__regex=r'\.[0-9n]').exclude(filename__endswith='.gz').exclude( filename__endswith='.xz').exclude( filename__endswith='.bz2').exclude(filename__endswith='.html') if username: pkg_ids = set(packages.values_list('id', flat=True)) bad_files = bad_files.filter(pkg__in=pkg_ids) bad_files = bad_files.values_list('pkg_id', flat=True).order_by().distinct() return packages.filter(id__in=set(bad_files)) def uncompressed_info(packages, username): # we don't worry about looking for '*.info-1', etc., given that an # uncompressed root page probably exists in the package anyway bad_files = PackageFile.objects.filter(is_directory=False, directory__endswith='/info/', filename__endswith='.info') if username: pkg_ids = set(packages.values_list('id', flat=True)) bad_files = bad_files.filter(pkg__in=pkg_ids) bad_files = bad_files.values_list('pkg_id', flat=True).order_by().distinct() return packages.filter(id__in=set(bad_files)) def unneeded_orphans(packages): owned = PackageRelation.objects.all().values('pkgbase') required = Depend.objects.all().values('name') # The two separate calls to exclude is required to do the right thing return packages.exclude(pkgbase__in=owned).exclude(pkgname__in=required) def mismatched_signature(packages): filtered = [] packages = packages.select_related( 'arch', 'repo', 'packager').filter(signature_bytes__isnull=False) known_keys = DeveloperKey.objects.select_related('owner').filter( owner__isnull=False) known_keys = {dk.key: dk for dk in known_keys} for package in packages: bad = False sig = package.signature dev_key = known_keys.get(sig.key_id, None) if dev_key: package.sig_by = dev_key.owner if dev_key.owner_id != package.packager_id: bad = True else: package.sig_by = sig.key_id bad = True if bad: filtered.append(package) return filtered def signature_time(packages): cutoff = timedelta(hours=24) filtered = [] packages = packages.select_related( 'arch', 'repo', 'packager').filter(signature_bytes__isnull=False) for package in packages: sig = package.signature sig_date = sig.creation_time.replace(tzinfo=pytz.utc) package.sig_date = sig_date.date() if sig_date > package.build_date + cutoff: filtered.append(package) return filtered REPORT_OLD = DeveloperReport( 'old', 'Old', 'Packages last built more than two years ago', old) REPORT_OUTOFDATE = DeveloperReport( 'long-out-of-date', 'Long Out-of-date', 'Packages marked out-of-date more than 30 days ago', outofdate) REPORT_BIG = DeveloperReport( 'big', 'Big', 'Packages with compressed size > 50 MiB', big, ['Compressed Size', 'Installed Size'], ['compressed_size_pretty', 'installed_size_pretty']) REPORT_BADCOMPRESS = DeveloperReport( 'badcompression', 'Bad Compression', 'Packages > 25 KiB with a compression ratio < 10%', badcompression, ['Compressed Size', 'Installed Size', 'Ratio', 'Type'], ['compressed_size_pretty', 'installed_size_pretty', 'ratio', 'compress_type']) REPORT_MAN = DeveloperReport('uncompressed-man', 'Uncompressed Manpages', 'Packages with uncompressed manpages', uncompressed_man) REPORT_INFO = DeveloperReport('uncompressed-info', 'Uncompressed Info Pages', 'Packages with uncompressed info pages', uncompressed_info) REPORT_ORPHANS = DeveloperReport( 'unneeded-orphans', 'Unneeded Orphans', 'Packages that have no maintainer and are not required by any ' + 'other package in any repository', unneeded_orphans, personal=False) REPORT_SIGNATURE = DeveloperReport( 'mismatched-signature', 'Mismatched Signatures', 'Packages where the signing key is unknown or signer != packager', mismatched_signature, ['Signed By', 'Packager'], ['sig_by', 'packager']) REPORT_SIG_TIME = DeveloperReport( 'signature-time', 'Signature Time', 'Packages where the signature timestamp is more than 24 hours ' + 'after the build timestamp', signature_time, ['Signature Date', 'Packager'], ['sig_date', 'packager']) def available_reports(): return (REPORT_OLD, REPORT_OUTOFDATE, REPORT_BIG, REPORT_BADCOMPRESS, REPORT_MAN, REPORT_INFO, REPORT_ORPHANS, REPORT_SIGNATURE, REPORT_SIG_TIME, )