"""Package only the explicitly selected saved jobs and referenced uploads.""" import argparse import hashlib import json from pathlib import Path import re import zipfile def strings(value): if isinstance(value, str): yield value elif isinstance(value, dict): for child in value.values(): yield from strings(child) elif isinstance(value, list): for child in value: yield from strings(child) def build(data, selection, output): if output.exists(): raise ValueError('refusing to overwrite bundle') files = {}; missing = set() models = json.loads(selection.read_text())['models'] for model in models: identity = model['job_id'] if not re.fullmatch('[0-9a-f]{24}', identity): raise ValueError('invalid job ID') name = f'8097-jobs/{identity}.json'; path = data / name files[name] = path job = json.loads(path.read_text()) if not (job.get('result') or {}).get('concrete_components'): raise ValueError('saved concrete missing') references = {match for s in strings(job) for match in re.findall(r'drawing-[0-9a-f]+\.(?:dxf|dwg|pdf|png|jpg|jpeg)', s, re.I)} for reference in references: source = data / 'uploads' / reference if source.is_file(): files['uploads/' + reference] = source else: missing.add(reference) manifest = {'schema': 'dms-shared-cache-v1', 'models': models, 'missing_referenced_uploads': sorted(missing), 'files': []} output.parent.mkdir(parents=True, exist_ok=True) with zipfile.ZipFile(output, 'x', compression=zipfile.ZIP_DEFLATED, compresslevel=6) as bundle: for name, path in sorted(files.items()): payload = path.read_bytes() manifest['files'].append({'path': name, 'bytes': len(payload), 'sha256': hashlib.sha256(payload).hexdigest()}) bundle.writestr(name, payload) bundle.writestr('manifest.json', json.dumps(manifest, ensure_ascii=False, indent=2)) return {'models': len(models), 'files': len(files), 'archive_bytes': output.stat().st_size, 'missing_referenced_uploads': sorted(missing)} if __name__ == '__main__': p=argparse.ArgumentParser(description=__doc__) p.add_argument('--data',type=Path,required=True);p.add_argument('--selection',type=Path,required=True) p.add_argument('--output',type=Path,required=True);a=p.parse_args() print(json.dumps(build(a.data,a.selection,a.output),ensure_ascii=False))