melodix-api / cleanup_b2_versions.py
GitHub Action
deploy from github actions
440bac0
Raw
History Blame Contribute Delete
5.1 kB
"""
cleanup_b2_versions.py
======================
Script de limpieza para eliminar versiones duplicadas de archivos en Backblaze B2.
Mantiene únicamente la versión más reciente de cada archivo bajo el prefijo 'stems/'.
Uso:
cd MelodixAPI
.\\venv_musical\\Scripts\\python cleanup_b2_versions.py [--dry-run]
"""
import os
import sys
import argparse
from dotenv import load_dotenv
load_dotenv()
import boto3
from botocore.config import Config
B2_KEY_ID = os.getenv("B2_KEY_ID")
B2_APPLICATION_KEY = os.getenv("B2_APPLICATION_KEY")
B2_ENDPOINT = os.getenv("B2_ENDPOINT")
B2_BUCKET_NAME = os.getenv("B2_BUCKET_NAME")
def get_client():
region = "us-east-005"
try:
parts = B2_ENDPOINT.replace("https://", "").replace("http://", "").split(".")
if len(parts) >= 2:
region = parts[1] if "s3" in parts[0] else parts[0]
except Exception:
pass
return boto3.client(
's3',
endpoint_url=B2_ENDPOINT,
aws_access_key_id=B2_KEY_ID,
aws_secret_access_key=B2_APPLICATION_KEY,
region_name=region,
config=Config(signature_version='s3', s3={'addressing_style': 'path'})
)
def cleanup_b2_duplicates(prefix="stems/", dry_run=False):
client = get_client()
print(f"{'[DRY RUN] ' if dry_run else ''}Escaneando versiones en B2 bucket '{B2_BUCKET_NAME}' bajo prefijo '{prefix}'...\n")
paginator = client.get_paginator('list_object_versions')
pages = paginator.paginate(Bucket=B2_BUCKET_NAME, Prefix=prefix)
# Agrupar versiones por key
versions_by_key = {}
for page in pages:
for version in page.get('Versions', []):
key = version['Key']
versions_by_key.setdefault(key, []).append(version)
for marker in page.get('DeleteMarkers', []):
key = marker['Key']
versions_by_key.setdefault(key, []).append(marker)
total_files = len(versions_by_key)
total_deleted = 0
total_kept = 0
to_delete = []
for key, versions in versions_by_key.items():
# Filtrar solo versiones reales (no delete markers) y ordenar por LastModified desc
real_versions = [v for v in versions if v.get('ETag')] # ETag presente = versión real
delete_markers = [v for v in versions if not v.get('ETag')]
if not real_versions and not delete_markers:
total_kept += 1
continue
if len(real_versions) <= 1 and not delete_markers:
total_kept += 1
continue
# Ordenar versiones reales por fecha desc, mantener la más reciente
real_versions.sort(key=lambda v: v['LastModified'], reverse=True)
old_versions = real_versions[1:] if real_versions else [] # Las demás son duplicados
if old_versions or delete_markers:
print(f" {key}: {len(real_versions)} versiones, {len(delete_markers)} delete markers -> Eliminando {len(old_versions) + len(delete_markers)} antiguas")
for v in old_versions:
to_delete.append({'Key': key, 'VersionId': v['VersionId']})
total_deleted += 1
for m in delete_markers:
to_delete.append({'Key': key, 'VersionId': m['VersionId']})
total_deleted += 1
else:
total_kept += 1
print(f"\nResumen: {total_files} archivos únicos, {total_deleted} versiones/markers a eliminar, {total_kept} sin duplicados")
if not to_delete:
print("OK: No hay duplicados que limpiar.")
return
if dry_run:
print(f"\n[DRY RUN] Se eliminarian {len(to_delete)} versiones antiguas (sin cambios reales).")
return
# Eliminar en lotes de 1000
confirmed = input(f"\nConfirmar eliminacion de {len(to_delete)} versiones antiguas? (s/N): ").strip().lower()
if confirmed != 's':
print("Cancelado.")
return
deleted_count = 0
for i in range(0, len(to_delete), 1000):
batch = to_delete[i:i+1000]
resp = client.delete_objects(
Bucket=B2_BUCKET_NAME,
Delete={'Objects': batch, 'Quiet': False}
)
deleted_count += len(resp.get('Deleted', []))
errors = resp.get('Errors', [])
if errors:
print(f" WARNING: {len(errors)} errores al eliminar:")
for e in errors:
print(f" - {e['Key']} v{e['VersionId']}: {e['Message']}")
print(f"\nOK: Limpieza completada: {deleted_count} versiones antiguas eliminadas de B2.")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Limpia versiones duplicadas en B2")
parser.add_argument("--dry-run", action="store_true", help="Solo muestra qué se eliminaría sin hacer cambios")
parser.add_argument("--prefix", default="stems/", help="Prefijo de B2 a escanear (default: stems/)")
args = parser.parse_args()
if not all([B2_KEY_ID, B2_APPLICATION_KEY, B2_ENDPOINT, B2_BUCKET_NAME]):
print("❌ Faltan variables de entorno B2. Verifica tu .env")
sys.exit(1)
cleanup_b2_duplicates(prefix=args.prefix, dry_run=args.dry_run)