diff --git a/bincio/serve/server.py b/bincio/serve/server.py index 762ae36..b030aa7 100644 --- a/bincio/serve/server.py +++ b/bincio/serve/server.py @@ -537,13 +537,24 @@ async def get_token(login_req: LoginRequest, request: Request) -> JSONResponse: @app.get("/api/feed") async def get_feed(user: User = Depends(_require_auth)) -> JSONResponse: - """Return the authenticated user's activity summaries (mobile feed sync).""" + """Return the authenticated user's activity summaries (mobile feed sync). + + _merged/index.json is a shard manifest (activities: []) when the user has + more than FEED_PAGE_SIZE activities. Collect from all shard files. + """ dd = _get_data_dir() user_dir = dd / user.handle for index_path in (user_dir / "_merged" / "index.json", user_dir / "index.json"): - if index_path.exists(): - index = json.loads(index_path.read_text()) - return JSONResponse({"activities": index.get("activities", [])}) + if not index_path.exists(): + continue + index = json.loads(index_path.read_text()) + activities: list[dict] = index.get("activities", []) + for shard in index.get("shards", []): + shard_path = index_path.parent / shard["url"] + if shard_path.exists(): + shard_doc = json.loads(shard_path.read_text()) + activities.extend(shard_doc.get("activities", [])) + return JSONResponse({"activities": activities}) return JSONResponse({"activities": []}) diff --git a/mobile/app/(tabs)/index.tsx b/mobile/app/(tabs)/index.tsx index 66af638..da20d17 100644 --- a/mobile/app/(tabs)/index.tsx +++ b/mobile/app/(tabs)/index.tsx @@ -18,10 +18,12 @@ export default function FeedScreen() { setSyncing(false); if (result.error) { setSyncMsg(result.error); + } else if (result.total === 0) { + setSyncMsg('No activities on instance'); } else if (result.synced === 0) { - setSyncMsg('Already up to date'); + setSyncMsg(`Up to date (${result.total} activities)`); } else { - setSyncMsg(`${result.synced} ${result.synced === 1 ? 'activity' : 'activities'} synced`); + setSyncMsg(`${result.synced} of ${result.total} activities synced`); } setTimeout(() => setSyncMsg(null), 3500); }, [db]); diff --git a/mobile/db/sync.ts b/mobile/db/sync.ts index 591542f..b5551d2 100644 --- a/mobile/db/sync.ts +++ b/mobile/db/sync.ts @@ -1,14 +1,14 @@ import type { SQLiteDatabase } from 'expo-sqlite'; import { getSetting, upsertRemoteActivity } from './queries'; -export type SyncResult = { synced: number; error?: string }; +export type SyncResult = { synced: number; total: number; error?: string }; export async function syncFeed(db: SQLiteDatabase): Promise { const instanceUrl = (await getSetting(db, 'instance_url'))?.replace(/\/$/, ''); const token = await getSetting(db, 'api_token'); if (!instanceUrl || !token) { - return { synced: 0, error: 'No instance configured — add one in Settings.' }; + return { synced: 0, total: 0, error: 'No instance configured — add one in Settings.' }; } let resp: Response; @@ -17,14 +17,14 @@ export async function syncFeed(db: SQLiteDatabase): Promise { headers: { Authorization: `Bearer ${token}` }, }); } catch { - return { synced: 0, error: 'Could not reach instance — check your connection.' }; + return { synced: 0, total: 0, error: 'Could not reach instance — check your connection.' }; } if (resp.status === 401) { - return { synced: 0, error: 'Session expired — reconnect in Settings.' }; + return { synced: 0, total: 0, error: 'Session expired — reconnect in Settings.' }; } if (!resp.ok) { - return { synced: 0, error: `Server error (${resp.status})` }; + return { synced: 0, total: 0, error: `Server error (${resp.status})` }; } const data: { activities?: RemoteSummary[] } = await resp.json(); @@ -48,7 +48,7 @@ export async function syncFeed(db: SQLiteDatabase): Promise { if (changed) synced++; } - return { synced }; + return { synced, total: activities.length }; } type RemoteSummary = {