feat(cli): watch files with event streams
Build and Push Docker Container / build-and-push (push) Successful in 2m3s
Build and Push Docker Container / build-and-push (push) Successful in 2m3s
- Add a private SSE endpoint that emits file change events for authenticated clients. - Replace the watch sleep loop with watchdog local file events and remote SSE triggers. - Debounce local event bursts and keep watch running after transient sync failures. - Add watchdog as a standalone CLI dependency and cover watch behavior in tests. - Bump NanoShare to 1.25.0 and the standalone CLI to 0.4.0.
This commit is contained in:
@@ -21,6 +21,7 @@ from .side.upload import upload_bp
|
||||
|
||||
from .api.cli_auth import cli_auth_bp
|
||||
from .api.download import api_download_bp
|
||||
from .api.events import api_events_bp
|
||||
|
||||
# Health
|
||||
from .api.health import health_bp
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
from my_modules.app.setup import LIMITER
|
||||
from my_modules.decoratory.header import token_required
|
||||
from quart import Blueprint, Response, current_app, stream_with_context
|
||||
|
||||
api_events_bp = Blueprint('api_events', __name__)
|
||||
|
||||
async def _snapshot(user_id: str) -> str:
|
||||
files = await current_app.convex.get_files(user_id)
|
||||
rows = []
|
||||
for item in files or []:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
rows.append({
|
||||
'file_id': item.get('file_id'),
|
||||
'file_name': item.get('file_name'),
|
||||
'file_path': item.get('file_path') or '',
|
||||
'file_size': item.get('file_size'),
|
||||
'expires_at': item.get('expires_at'),
|
||||
'updated_at': item.get('updated_at') or item.get('uploaded_at'),
|
||||
})
|
||||
rows.sort(key=lambda row: str(row.get('file_id') or ''))
|
||||
return json.dumps(rows, sort_keys=True, separators=(',', ':'))
|
||||
|
||||
@api_events_bp.get('/api/files/events')
|
||||
@LIMITER.limit('6 per minute;60 per hour;')
|
||||
@token_required(['files', 'mesh'])
|
||||
async def api_file_events(user: dict):
|
||||
user_id = user['sub']
|
||||
|
||||
@stream_with_context
|
||||
async def stream():
|
||||
previous = await _snapshot(user_id)
|
||||
yield 'event: ready\ndata: {}\n\n'
|
||||
while True:
|
||||
await asyncio.sleep(30)
|
||||
current = await _snapshot(user_id)
|
||||
if current != previous:
|
||||
previous = current
|
||||
yield f'event: files.changed\ndata: {current}\n\n'
|
||||
else:
|
||||
yield ': keepalive\n\n'
|
||||
|
||||
return Response(stream(), content_type='text/event-stream', headers={
|
||||
'Cache-Control': 'no-cache',
|
||||
'X-Accel-Buffering': 'no',
|
||||
})
|
||||
Reference in New Issue
Block a user