From 2f663acc32576084fe9e4e5f4bfd82f8021cd612 Mon Sep 17 00:00:00 2001 From: Valentin Niess Date: Wed, 21 May 2025 23:04:04 +0200 Subject: [PATCH] Cache management --- python_appimage/__main__.py | 25 +++++++-- python_appimage/commands/cache/__init__.py | 0 python_appimage/commands/cache/clean.py | 64 ++++++++++++++++++++++ python_appimage/commands/cache/list.py | 41 ++++++++++++++ python_appimage/manylinux/download.py | 4 +- 5 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 python_appimage/commands/cache/__init__.py create mode 100644 python_appimage/commands/cache/clean.py create mode 100644 python_appimage/commands/cache/list.py diff --git a/python_appimage/__main__.py b/python_appimage/__main__.py index 314e0bb..409b0ae 100644 --- a/python_appimage/__main__.py +++ b/python_appimage/__main__.py @@ -34,16 +34,31 @@ def main(): parser.add_argument('-v', '--verbose', help='print extra information', dest='verbosity', action='store_const', const='DEBUG') - install_parser = subparsers.add_parser('install', - description='Install binary dependencies') - install_parser.add_argument('binary', nargs='+', - choices=binaries, help='one or more binary name') - build_parser = subparsers.add_parser('build', description='Build a Python appimage') build_subparsers = build_parser.add_subparsers(title='type', help='Type of AppImage build', dest='sub_command') + cache_parser = subparsers.add_parser('cache', + description='Manage Python appimage cache') + cache_subparsers = cache_parser.add_subparsers(title='operation', + help='Type of cache operation', dest='sub_command') + + cache_clean_parser = cache_subparsers.add_parser('clean', + description='Clean cached image(s)') + cache_clean_parser.add_argument('tags', nargs='*', + help='manylinux image tag(s) (e.g. 2014_x86_64)') + cache_clean_parser.add_argument('-a', '--all', action='store_true', + help='remove all image(s) data') + + cache_list_parser = cache_subparsers.add_parser('list', + description='List cached image(s)') + + install_parser = subparsers.add_parser('install', + description='Install binary dependencies') + install_parser.add_argument('binary', nargs='+', + choices=binaries, help='one or more binary name') + build_local_parser = build_subparsers.add_parser('local', description='Bundle a local Python installation') build_local_parser.add_argument('-d', '--destination', diff --git a/python_appimage/commands/cache/__init__.py b/python_appimage/commands/cache/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python_appimage/commands/cache/clean.py b/python_appimage/commands/cache/clean.py new file mode 100644 index 0000000..9ad97f8 --- /dev/null +++ b/python_appimage/commands/cache/clean.py @@ -0,0 +1,64 @@ +import glob +import json +import os +from pathlib import Path +import subprocess + +from ...utils.deps import CACHE_DIR +from ...utils.fs import remove_file, remove_tree +from ...utils.log import log + + +__all__ = ['execute'] + + +def _unpack_args(args): + '''Unpack command line arguments + ''' + return (args.tags, args.all) + + +def execute(images, all_): + '''Clean cached image(s) + ''' + + cache = Path(CACHE_DIR) + + if not images: + images = [image[9:] for image in sorted(os.listdir(cache / + 'share/images'))] + + for image in images: + try: + image, tag = image.rsplit(':', 1) + except ValueError: + tag = None + + if not image.replace('_', '').isalnum(): + raise ValueError(f'bad image tag ({image})') + + path = cache / f'share/images/manylinux{image}' + if not path.exists(): + raise ValueError(f'no such image ({image})') + + if tag is None: + if not all_: + path = path / 'extracted' + remove_tree(str(path)) + else: + tag_file = path / f'tags/{tag}.json' + if not tag_file.exists(): + raise ValueError(f'no such image ({image}:{tag})') + + if all_: + with tag_file.open() as f: + layers = json.load(f)["layers"] + for layer in layers: + layer = path / f'layers/{layer}.tar.gz' + if layer.exists(): + remove_file(str(layer)) + remove_file(str(tag_file)) + else: + path = cache / f'share/images/{image}/extracted/{tag}' + if path.exists(): + remove_tree(str(path)) diff --git a/python_appimage/commands/cache/list.py b/python_appimage/commands/cache/list.py new file mode 100644 index 0000000..6101052 --- /dev/null +++ b/python_appimage/commands/cache/list.py @@ -0,0 +1,41 @@ +import glob +import os +from pathlib import Path +import subprocess + +from ...utils.deps import CACHE_DIR +from ...utils.log import log + + +__all__ = ['execute'] + + +def _unpack_args(args): + '''Unpack command line arguments + ''' + return tuple() + + +def execute(): + '''List cached image(s) + ''' + + cache = Path(CACHE_DIR) + + images = sorted(os.listdir(cache / 'share/images')) + for image in images: + tags = ', '.join(( + tag[:-5] for tag in \ + sorted(os.listdir(cache / f'share/images/{image}/tags')) + )) + if not tags: + continue + path = cache / f'share/images/{image}' + memory = _getsize(path) + log('LIST', f'{image} ({tags}) [{memory}]') + + +def _getsize(path: Path): + r = subprocess.run(f'du -sh {path}', capture_output=True, check=True, + shell=True) + return r.stdout.decode().split(None, 1)[0] diff --git a/python_appimage/manylinux/download.py b/python_appimage/manylinux/download.py index bfc2cf2..29ffeb0 100644 --- a/python_appimage/manylinux/download.py +++ b/python_appimage/manylinux/download.py @@ -117,8 +117,8 @@ class Downloader: with tempfile.TemporaryDirectory() as tmpdir: workdir = Path(tmpdir) for i, hash_ in enumerate(missing): - log('DOWNLOAD', f'{self.image} ({tag}) ' - f'[{i + 1} / {len(missing)}]') + debug('DOWNLOAD', f'{self.image}:{tag} ' + f'[{i + 1} / {len(missing)}]') filename = f'{hash_}.tar.gz' url = f'https://quay.io/v2/{repository}/blobs/sha256:{hash_}'