reVCDOS / additions /packed.py
Avitesh Murmu
Complete deployment with all assets and server modules
0d97019
"""
Module for serving files from a PackedArchive.
Provides similar interface to cache.py but reads from packed .bin archives.
Supports:
- Serving files from vcsky/ and vcbr/ paths inside the archive
- Brotli compression passthrough when client supports it (Accept-Encoding: br)
- On-the-fly decompression when client doesn't support brotli
- Proper handling of .br files (stored without additional compression)
- Auto-download from URL if archive file is not present locally
"""
import os
import sys
from typing import Optional
from urllib.parse import urlparse
import httpx
import brotli
from fastapi import Request
from fastapi.responses import Response, StreamingResponse
# Import PackedArchive from utils
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'utils'))
from utils.packer_brotli import PackedArchive
# Global archive instance (initialized by init_packed_archive)
_archive: Optional[PackedArchive] = None
def _is_url(path: str) -> bool:
"""Check if the path is a URL."""
return path.startswith("http://") or path.startswith("https://")
def _get_filename_from_url(url: str) -> str:
"""Extract filename from URL."""
parsed = urlparse(url)
path = parsed.path
filename = os.path.basename(path)
if not filename:
filename = "packed.bin"
return filename
async def _download_file(url: str, dest_path: str) -> bool:
"""
Download a file from URL to destination path.
Args:
url: URL to download from
dest_path: Local path to save the file
Returns:
True if download succeeded, False otherwise
"""
print(f"Downloading archive from {url}...")
try:
async with httpx.AsyncClient(timeout=httpx.Timeout(300.0), follow_redirects=True) as client:
async with client.stream('GET', url) as response:
response.raise_for_status()
content_length = response.headers.get('content-length')
total_size = int(content_length) if content_length else 0
downloaded = 0
with open(dest_path, 'wb') as f:
async for chunk in response.aiter_bytes(65536):
f.write(chunk)
downloaded += len(chunk)
if total_size > 0:
percent = (downloaded / total_size) * 100
print(f"\r Downloaded: {downloaded / 1024 / 1024:.1f} MB ({percent:.1f}%)", end="", flush=True)
else:
print(f"\r Downloaded: {downloaded / 1024 / 1024:.1f} MB", end="", flush=True)
print() # New line after download complete
print(f" Saved to: {dest_path}")
return True
except httpx.HTTPStatusError as e:
print(f"Failed to download: HTTP {e.response.status_code}")
return False
except Exception as e:
print(f"Error downloading file: {e}")
return False
async def resolve_packed_source(source: str) -> Optional[str]:
"""
Resolve packed archive source to local file path.
If source is a URL:
- Extract filename from URL
- Check if file exists locally and has size > 0
- If not, download it from URL
- Return local file path
If source is a local path:
- Return it as-is
Args:
source: URL or local file path
Returns:
Local file path, or None if download failed
"""
if not _is_url(source):
# Local file path
return source
# It's a URL - extract filename and check if we have it locally
filename = _get_filename_from_url(source)
local_path = filename # Save in current directory
# Check if file exists and has size > 0
if os.path.isfile(local_path) and os.path.getsize(local_path) > 0:
print(f"Using existing archive: {local_path} ({os.path.getsize(local_path)} bytes)")
return local_path
# File doesn't exist or is empty - download it
if await _download_file(source, local_path):
return local_path
return None
async def init_packed_archive(source: str) -> Optional[PackedArchive]:
"""
Initialize the packed archive.
Must be called before using get_packed_file().
Supports both local file paths and URLs.
If a URL is provided, the file will be downloaded if not present locally.
Args:
source: Path to the .bin archive file or URL to download from
Returns:
Initialized PackedArchive instance, or None if failed
"""
global _archive
# Resolve source to local path (download if needed)
archive_path = await resolve_packed_source(source)
if archive_path is None:
print(f"Failed to resolve packed archive source: {source}")
return None
if not os.path.isfile(archive_path):
print(f"Archive file not found: {archive_path}")
return None
_archive = PackedArchive(archive_path)
await _archive.init()
print(f"Loaded packed archive: {archive_path}")
print(f" Folders: {len(_archive.list_folders())}")
print(f" Files: {len(_archive.list_files())}")
return _archive
def get_archive() -> Optional[PackedArchive]:
"""Get the global archive instance."""
return _archive
def is_initialized() -> bool:
"""Check if the archive is initialized."""
return _archive is not None and _archive._initialized
def _client_accepts_brotli(request: Request) -> bool:
"""Check if client accepts brotli encoding."""
accept_encoding = request.headers.get("accept-encoding", "")
return "br" in accept_encoding.lower()
def _get_response_headers(use_brotli: bool, media_type: str) -> dict:
"""Get response headers, optionally with brotli encoding."""
headers = {
"Cross-Origin-Opener-Policy": "same-origin",
"Cross-Origin-Embedder-Policy": "require-corp",
"Content-Type": media_type
}
if use_brotli:
headers["Content-Encoding"] = "br"
return headers
def _is_br_file(path: str) -> bool:
"""Check if the file is a .br (pre-compressed brotli) file."""
return path.lower().endswith(".br")
def _get_media_type(path: str) -> str:
"""
Get appropriate media type based on file extension.
For .br files, returns the media type of the underlying content.
"""
lower_path = path.lower()
# Handle .br files - get media type of what's inside
if lower_path.endswith(".wasm.br"):
return "application/wasm"
if lower_path.endswith(".js.br"):
return "application/javascript"
if lower_path.endswith(".json.br"):
return "application/json"
if lower_path.endswith(".html.br"):
return "text/html"
if lower_path.endswith(".css.br"):
return "text/css"
if lower_path.endswith(".br"):
# Generic .br file - use octet-stream
return "application/octet-stream"
# Non-.br files
if lower_path.endswith(".wasm"):
return "application/wasm"
if lower_path.endswith(".js"):
return "application/javascript"
if lower_path.endswith(".json"):
return "application/json"
if lower_path.endswith(".html"):
return "text/html"
if lower_path.endswith(".css"):
return "text/css"
if lower_path.endswith(".png"):
return "image/png"
if lower_path.endswith(".jpg") or lower_path.endswith(".jpeg"):
return "image/jpeg"
if lower_path.endswith(".gif"):
return "image/gif"
if lower_path.endswith(".svg"):
return "image/svg+xml"
if lower_path.endswith(".mp3"):
return "audio/mpeg"
if lower_path.endswith(".wav"):
return "audio/wav"
if lower_path.endswith(".ogg"):
return "audio/ogg"
return "application/octet-stream"
async def get_packed_file(path: str, request: Request) -> Optional[Response]:
"""
Get a file from the packed archive.
How .br files work:
- .br files are stored in the archive WITHOUT additional brotli compression
- archive.open(path) returns the raw .br file content (already brotli-compressed)
- If client accepts br: send .br data with Content-Encoding: br
- If client doesn't accept br: decompress .br data and send plain
How regular files work:
- Regular files are stored with brotli compression in the archive
- If client accepts br: keep_brotli=True returns compressed data, send with Content-Encoding: br
- If client doesn't accept br: keep_brotli=False decompresses, send plain
Args:
path: Path to the file inside the archive (e.g., "vcsky/fetched/model.txd")
request: FastAPI request object to check Accept-Encoding header
Returns:
Response with file data, or None if file not found or archive not initialized
"""
if not is_initialized():
return None
# Check if file exists in archive
if not _archive.exists(path):
return None
# Check if client accepts brotli
client_accepts_br = _client_accepts_brotli(request)
# Check if file is a .br file
is_br_file = _is_br_file(path)
# Get media type based on file extension
media_type = _get_media_type(path)
try:
if is_br_file:
# .br files are stored as-is (no archive compression)
# archive.open() returns the raw .br content
async with _archive.open(path, keep_brotli=False) as f:
br_data = f.read()
if client_accepts_br:
# Send the .br data with Content-Encoding: br
headers = _get_response_headers(use_brotli=True, media_type=media_type)
return Response(content=br_data, headers=headers)
else:
# Decompress .br for client
decompressed_data = brotli.decompress(br_data)
headers = _get_response_headers(use_brotli=False, media_type=media_type)
return Response(content=decompressed_data, headers=headers)
else:
# Regular files: use archive's brotli compression
if client_accepts_br:
async with _archive.open(path, keep_brotli=True) as f:
data = f.read()
headers = _get_response_headers(use_brotli=True, media_type=media_type)
else:
async with _archive.open(path, keep_brotli=False) as f:
data = f.read()
headers = _get_response_headers(use_brotli=False, media_type=media_type)
return Response(content=data, headers=headers)
except FileNotFoundError:
return None
except Exception as e:
print(f"Error reading file from archive: {path} - {e}")
return None
async def get_packed_file_streaming(path: str, request: Request, chunk_size: int = 65536) -> Optional[StreamingResponse]:
"""
Get a file from the packed archive as a streaming response.
Args:
path: Path to the file inside the archive
request: FastAPI request object to check Accept-Encoding header
chunk_size: Size of chunks for streaming (default: 64KB)
Returns:
StreamingResponse with file data, or None if file not found
"""
if not is_initialized():
return None
if not _archive.exists(path):
return None
client_accepts_br = _client_accepts_brotli(request)
is_br = _is_br_file(path)
media_type = _get_media_type(path)
async def generate():
try:
if is_br:
# .br file: stored as-is, open returns raw .br data
async with _archive.open(path, keep_brotli=False) as f:
br_data = f.data
if client_accepts_br:
# Send .br data directly
for i in range(0, len(br_data), chunk_size):
yield br_data[i:i + chunk_size]
else:
# Decompress for client
decompressed_data = brotli.decompress(br_data)
for i in range(0, len(decompressed_data), chunk_size):
yield decompressed_data[i:i + chunk_size]
else:
# Regular file
async with _archive.open(path, keep_brotli=client_accepts_br) as f:
data = f.data
for i in range(0, len(data), chunk_size):
yield data[i:i + chunk_size]
except Exception as e:
print(f"Error streaming file from archive: {path} - {e}")
headers = _get_response_headers(use_brotli=client_accepts_br, media_type=media_type)
return StreamingResponse(generate(), headers=headers)
def file_exists(path: str) -> bool:
"""
Check if a file exists in the packed archive.
Args:
path: Path to the file inside the archive
Returns:
True if file exists, False otherwise
"""
if not is_initialized():
return False
return _archive.exists(path)
def list_files(folder: Optional[str] = None) -> list:
"""
List files in the archive.
Args:
folder: Optional folder path to filter by
Returns:
List of file paths
"""
if not is_initialized():
return []
return _archive.list_files(folder)
def list_folders() -> list:
"""
List all folders in the archive.
Returns:
List of folder paths
"""
if not is_initialized():
return []
return _archive.list_folders()