diff --git a/.gitignore b/.gitignore index 35826d3c..8d81a51f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # MAC OS .DS_Store +# VS Code +.vscode/ + # Vim Gitignore ## Swap [._]*.s[a-v][a-z] diff --git a/bench/commands/__init__.py b/bench/commands/__init__.py index 5ef14212..1daf6146 100755 --- a/bench/commands/__init__.py +++ b/bench/commands/__init__.py @@ -72,6 +72,7 @@ bench_command.add_command(switch_to_develop) from bench.commands.utils import ( + app_cache_helper, backup_all_sites, bench_src, disable_production, @@ -108,6 +109,7 @@ bench_command.add_command(disable_production) bench_command.add_command(bench_src) bench_command.add_command(find_benches) bench_command.add_command(migrate_env) +bench_command.add_command(app_cache_helper) from bench.commands.setup import setup diff --git a/bench/commands/utils.py b/bench/commands/utils.py index 9882e8f0..5b1e5c1f 100644 --- a/bench/commands/utils.py +++ b/bench/commands/utils.py @@ -176,3 +176,21 @@ def migrate_env(python, backup=True): from bench.utils.bench import migrate_env migrate_env(python=python, backup=backup) + + +@click.command("app-cache", help="View or remove items belonging to bench get-app cache") +@click.option("--clear", is_flag=True, default=False, help="Remove all items") +@click.option( + "--remove-app", + default="", + help="Removes all items that match provided app name", +) +@click.option( + "--remove-hash", + default="", + help="Removes all items that matches provided commit-hash", +) +def app_cache_helper(clear=False, remove_app="", remove_hash=""): + from bench.utils.bench import cache_helper + + cache_helper(clear, remove_app, remove_hash) diff --git a/bench/utils/bench.py b/bench/utils/bench.py index 306fa623..188283b2 100644 --- a/bench/utils/bench.py +++ b/bench/utils/bench.py @@ -4,11 +4,13 @@ import json import logging import os import re +import shutil import subprocess import sys from functools import lru_cache from glob import glob from json.decoder import JSONDecodeError +from pathlib import Path # imports - third party imports import click @@ -16,7 +18,8 @@ import click # imports - module imports import bench from bench.exceptions import PatchError, ValidationError -from bench.utils import exec_cmd, get_bench_name, get_cmd_output, log, which +from bench.utils import (exec_cmd, get_bench_cache_path, get_bench_name, + get_cmd_output, log, which) logger = logging.getLogger(bench.PROJECT_NAME) @@ -353,13 +356,14 @@ def build_assets(bench_path=".", app=None, using_cached=False): command = "bench build" if app: command += f" --app {app}" - + env = {"BENCH_DEVELOPER": "1"} if using_cached: env["USING_CACHED"] = "1" - + exec_cmd(command, cwd=bench_path, env=env) + def handle_version_upgrade(version_upgrade, bench_path, force, reset, conf): from bench.utils import log, pause_exec @@ -638,3 +642,117 @@ To switch to your required branch, run the following commands: bench switch-to-b ) sys.exit(1) + + +def cache_helper(clear=False, remove_app="", remove_hash="") -> None: + can_remove = bool(remove_hash or remove_app) + if not clear and not can_remove: + cache_list() + elif can_remove: + cache_remove(remove_app, remove_hash) + elif clear: + cache_clear() + else: + pass # unreachable + + +def cache_list() -> None: + from datetime import datetime + + tot_size = 0 + tot_items = 0 + + printed_header = False + for item in get_bench_cache_path("apps").iterdir(): + if item.suffix not in [".tar", ".tgz"]: + continue + + stat = item.stat() + size_mb = stat.st_size / 1_000_000 + created = datetime.fromtimestamp(stat.st_ctime) + accessed = datetime.fromtimestamp(stat.st_atime) + + app = item.name.split("-")[0] + tot_items += 1 + tot_size += stat.st_size + compressed = item.suffix == ".tgz" + + if not printed_header: + click.echo( + f"{'APP':15} " + f"{'FILE':25} " + f"{'SIZE':>13} " + f"{'COMPRESSED'} " + f"{'CREATED':19} " + f"{'ACCESSED':19} " + ) + printed_header = True + + click.echo( + f"{app:15} " + f"{item.name:25} " + f"{size_mb:10.3f} MB " + f"{str(compressed):10} " + f"{created:%Y-%m-%d %H:%M:%S} " + f"{accessed:%Y-%m-%d %H:%M:%S} " + ) + if tot_items: + click.echo(f"Total size {tot_size / 1_000_000:.3f} MB belonging to {tot_items} items") + else: + click.echo("No cached items") + + +def cache_remove(app: str = "", hash: str = "") -> None: + rem_items = 0 + rem_size = 0 + for item in get_bench_cache_path("apps").iterdir(): + if not should_remove_item(item, app, hash): + continue + + rem_items += 1 + rem_size += item.stat().st_size + item.unlink(True) + click.echo(f"Removed {item.name}") + + if rem_items: + click.echo(f"Cleared {rem_size / 1_000_000:.3f} MB belonging to {rem_items} items") + else: + click.echo("No items removed") + + +def should_remove_item(item: Path, app: str, hash: str) -> bool: + if item.suffix not in [".tar", ".tgz"]: + return False + + name = item.name + if app and hash and name.startswith(f"{app}-{hash[:10]}."): + return True + + if app and name.startswith(f"{app}-"): + return True + + if hash and f"-{hash[:10]}." in name: + return True + + return False + + +def cache_clear() -> None: + cache_path = get_bench_cache_path("apps") + tot_items = len(os.listdir(cache_path)) + if not tot_items: + click.echo("No cached items") + return + + tot_size = get_dir_size(cache_path) + shutil.rmtree(cache_path) + + rem_items = tot_items - len(os.listdir(cache_path)) + rem_size = tot_size - get_dir_size(cache_path) + + if rem_items: + click.echo(f"Cleared {rem_size / 1_000_000:.3f} MB belonging to {rem_items} items") + + +def get_dir_size(p: Path) -> int: + return sum(i.stat(follow_symlinks=False).st_size for i in p.iterdir())