基本功能完成
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import * as SQLite from 'expo-sqlite';
|
||||
import * as FileSystem from 'expo-file-system/legacy';
|
||||
import type { MeasurementFrame, MeasurementMeta, SetupConfig, GateConfig } from '../protocol/types';
|
||||
import { AMP_GAIN } from '../protocol/constants';
|
||||
import * as BinLoader from './BinLoader';
|
||||
import * as TemBundle from './TemBundle';
|
||||
|
||||
@@ -70,10 +71,37 @@ async function getDb(): Promise<SQLite.SQLiteDatabase> {
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_fs ON frames(session_id);
|
||||
`);
|
||||
await runMigrations(_db);
|
||||
}
|
||||
return _db;
|
||||
}
|
||||
|
||||
async function runMigrations(db: SQLite.SQLiteDatabase) {
|
||||
const row = await db.getFirstAsync<{ user_version: number }>('PRAGMA user_version');
|
||||
let version = row?.user_version ?? 0;
|
||||
|
||||
if (version < 1) {
|
||||
try { await db.runAsync('ALTER TABLE sessions ADD COLUMN project_id TEXT'); } catch {}
|
||||
await db.runAsync('PRAGMA user_version = 1');
|
||||
version = 1;
|
||||
}
|
||||
if (version < 2) {
|
||||
const orphans = await db.getAllAsync<{ session_id: string }>(
|
||||
'SELECT session_id FROM sessions WHERE project_id IS NULL',
|
||||
);
|
||||
if (orphans.length > 0) {
|
||||
const defId = generateProjectId();
|
||||
await db.runAsync(
|
||||
'INSERT INTO projects (project_id, name, created_at) VALUES (?,?,?)',
|
||||
[defId, '默认工程', Date.now()],
|
||||
);
|
||||
await db.runAsync('UPDATE sessions SET project_id = ? WHERE project_id IS NULL', [defId]);
|
||||
}
|
||||
await db.runAsync('PRAGMA user_version = 2');
|
||||
version = 2;
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureDataDir() {
|
||||
const info = await FileSystem.getInfoAsync(DATA_DIR);
|
||||
if (!info.exists) await FileSystem.makeDirectoryAsync(DATA_DIR, { intermediates: true });
|
||||
@@ -84,23 +112,44 @@ export async function initStorage(): Promise<void> {
|
||||
await ensureDataDir();
|
||||
}
|
||||
|
||||
export async function hasAnyProject(): Promise<boolean> {
|
||||
const d = await getDb();
|
||||
const row = await d.getFirstAsync<{ cnt: number }>('SELECT COUNT(*) AS cnt FROM projects');
|
||||
return (row?.cnt ?? 0) > 0;
|
||||
}
|
||||
|
||||
export async function getFirstProject(): Promise<ProjectInfo | null> {
|
||||
const d = await getDb();
|
||||
const r = await d.getFirstAsync<{ project_id: string; name: string; created_at: number }>(
|
||||
'SELECT project_id, name, created_at FROM projects ORDER BY created_at DESC LIMIT 1',
|
||||
);
|
||||
if (!r) return null;
|
||||
return { projectId: r.project_id, name: r.name, createdAt: r.created_at, lineCount: 0 };
|
||||
}
|
||||
|
||||
// ── Project CRUD ────────────────────────────────────────────────────────────
|
||||
|
||||
function generateProjectId(): string {
|
||||
const now = new Date();
|
||||
const pad = (n: number, d = 2) => String(n).padStart(d, '0');
|
||||
const rand = Math.random().toString(36).slice(2, 6);
|
||||
return (
|
||||
`P${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}` +
|
||||
`${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`
|
||||
`${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}` +
|
||||
`${pad(now.getMilliseconds(), 3)}_${rand}`
|
||||
);
|
||||
}
|
||||
|
||||
function sanitizeName(name: string): string {
|
||||
return name.trim().replace(/[\/\\:*?"<>|.\x00]/g, '_').slice(0, 50);
|
||||
}
|
||||
|
||||
export async function createProject(name: string): Promise<string> {
|
||||
const d = await getDb();
|
||||
const id = generateProjectId();
|
||||
await d.runAsync(
|
||||
'INSERT INTO projects (project_id, name, created_at) VALUES (?,?,?)',
|
||||
[id, name.trim(), Date.now()],
|
||||
[id, sanitizeName(name), Date.now()],
|
||||
);
|
||||
return id;
|
||||
}
|
||||
@@ -132,6 +181,19 @@ export async function renameProject(projectId: string, name: string): Promise<vo
|
||||
|
||||
export async function deleteProject(projectId: string): Promise<void> {
|
||||
const d = await getDb();
|
||||
const sessions = await d.getAllAsync<{ session_id: string }>(
|
||||
'SELECT session_id FROM sessions WHERE project_id = ?', [projectId],
|
||||
);
|
||||
for (const s of sessions) {
|
||||
const frames = await d.getAllAsync<{ bin_path: string | null }>(
|
||||
'SELECT bin_path FROM frames WHERE session_id = ?', [s.session_id],
|
||||
);
|
||||
for (const f of frames) {
|
||||
if (f.bin_path) { try { await FileSystem.deleteAsync(f.bin_path, { idempotent: true }); } catch {} }
|
||||
}
|
||||
await d.runAsync('DELETE FROM frames WHERE session_id = ?', [s.session_id]);
|
||||
}
|
||||
await d.runAsync('DELETE FROM sessions WHERE project_id = ?', [projectId]);
|
||||
await d.runAsync('DELETE FROM projects WHERE project_id = ?', [projectId]);
|
||||
}
|
||||
|
||||
@@ -215,7 +277,7 @@ export async function persistFrame(frame: MeasurementFrame, sessionId: string):
|
||||
} catch { binPath = null; }
|
||||
|
||||
await d.runAsync(
|
||||
`INSERT OR REPLACE INTO frames (
|
||||
`INSERT OR IGNORE INTO frames (
|
||||
frame_id, session_id, ts, utc, lon, lat, alt,
|
||||
ch_num, acc_num, gain, gps_st, sd_st, amp_ratio,
|
||||
current, temp, batt, roll, pitch, yaw, src_mode, bin_path
|
||||
@@ -381,7 +443,9 @@ export async function exportProject(
|
||||
const bundleBytes = await TemBundle.packBundle(manifest, bins);
|
||||
|
||||
await ensureDataDir();
|
||||
const outPath = DATA_DIR + `TEM_${proj.name}_${Date.now()}.tem`;
|
||||
const safeName = sanitizeName(proj.name);
|
||||
const ts = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
||||
const outPath = DATA_DIR + `TEM_${safeName}_${ts}.tem`;
|
||||
await BinLoader.writeFileBytes(outPath, bundleBytes);
|
||||
return outPath;
|
||||
}
|
||||
@@ -473,7 +537,7 @@ export async function importProject(
|
||||
[
|
||||
frameId, newSessionId, meta.timestamp,
|
||||
meta.utc, meta.longitude, meta.latitude, meta.altitude,
|
||||
meta.channelNum, meta.accNum, meta.ampRatio,
|
||||
meta.channelNum, meta.accNum, AMP_GAIN[meta.ampRatio] ?? 1,
|
||||
meta.gpsStatus, meta.sdStatus, meta.ampRatio,
|
||||
meta.current, meta.temperature, meta.batteryVolt,
|
||||
meta.roll, meta.pitch, meta.yaw, meta.srcMode, binPath,
|
||||
|
||||
Reference in New Issue
Block a user