feat(mobile/upload): send original FIT file via /api/upload/raw when available
When original_path is set (i.e. the original FIT/GPX/TCX is still on disk),
upload via POST /api/upload/raw { filename, base64 } so the server re-extracts
with DEM elevation correction. Falls back to /api/upload/bas (pre-extracted
BAS JSON) when original_path is null or the file has been deleted.
This commit is contained in:
+37
-14
@@ -1,3 +1,4 @@
|
||||
import * as FileSystem from 'expo-file-system/legacy';
|
||||
import type { SQLiteDatabase } from 'expo-sqlite';
|
||||
import { getSetting, upsertRemoteActivity } from './queries';
|
||||
|
||||
@@ -158,8 +159,14 @@ async function uploadLocalActivities(
|
||||
token: string,
|
||||
onProgress?: (n: number, total: number) => void,
|
||||
): Promise<{ uploaded: number; failed: number }> {
|
||||
const rows = db.getAllSync<{ id: string; detail_json: string; timeseries_json: string | null; geojson: string | null }>(
|
||||
`SELECT id, detail_json, timeseries_json, geojson
|
||||
const rows = db.getAllSync<{
|
||||
id: string;
|
||||
detail_json: string;
|
||||
timeseries_json: string | null;
|
||||
geojson: string | null;
|
||||
original_path: string | null;
|
||||
}>(
|
||||
`SELECT id, detail_json, timeseries_json, geojson, original_path
|
||||
FROM activities WHERE origin = 'local' AND synced_at IS NULL`,
|
||||
);
|
||||
|
||||
@@ -173,28 +180,44 @@ async function uploadLocalActivities(
|
||||
const row = rows[i];
|
||||
onProgress?.(i + 1, total);
|
||||
try {
|
||||
const detail = JSON.parse(row.detail_json);
|
||||
const activity = { id: row.id, ...detail };
|
||||
let resp: Response;
|
||||
|
||||
const body: Record<string, unknown> = { activity };
|
||||
if (row.timeseries_json) body.timeseries = JSON.parse(row.timeseries_json);
|
||||
if (row.geojson) body.geojson = JSON.parse(row.geojson);
|
||||
// Prefer raw upload when the original FIT/GPX/TCX file is still on disk.
|
||||
// The server re-extracts it with DEM elevation correction, producing better data.
|
||||
const useRaw = row.original_path !== null &&
|
||||
(await FileSystem.getInfoAsync(row.original_path)).exists;
|
||||
|
||||
const resp = await fetch(`${instanceUrl}/api/upload/bas`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (useRaw) {
|
||||
const filename = row.original_path!.split('/').pop() ?? 'activity.fit';
|
||||
const base64 = await FileSystem.readAsStringAsync(row.original_path!, {
|
||||
encoding: FileSystem.EncodingType.Base64,
|
||||
});
|
||||
resp = await fetch(`${instanceUrl}/api/upload/raw`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ filename, base64 }),
|
||||
});
|
||||
} else {
|
||||
const detail = JSON.parse(row.detail_json);
|
||||
const body: Record<string, unknown> = { activity: { id: row.id, ...detail } };
|
||||
if (row.timeseries_json) body.timeseries = JSON.parse(row.timeseries_json);
|
||||
if (row.geojson) body.geojson = JSON.parse(row.geojson);
|
||||
resp = await fetch(`${instanceUrl}/api/upload/bas`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
|
||||
if (resp.ok) {
|
||||
await db.runAsync(`UPDATE activities SET synced_at = ? WHERE id = ?`, [now, row.id]);
|
||||
uploaded++;
|
||||
} else {
|
||||
console.warn(`upload/bas ${row.id}: HTTP ${resp.status}`);
|
||||
console.warn(`upload ${row.id}: HTTP ${resp.status}`);
|
||||
failed++;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`upload/bas ${row.id}:`, err);
|
||||
console.warn(`upload ${row.id}:`, err);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user