add sqlite
This commit is contained in:
+7
-3
@@ -4,10 +4,14 @@ Read the exact versioned docs at https://docs.expo.dev/versions/v57.0.0/ before
|
|||||||
|
|
||||||
# Local storage
|
# Local storage
|
||||||
|
|
||||||
Persistence is SQLite-backed via `services/sqliteStorage.ts` (`expo-sqlite`, database `dot.db`).
|
Persistence is SQLite-backed via `services/db/` (`expo-sqlite`, database `dot.db`).
|
||||||
|
|
||||||
- Tables: `folders`, `files`, `user_preferences` (schema migrated via `PRAGMA user_version`).
|
- `services/db/client.ts` — connection lifecycle: `getDatabase()`, `closeDatabase()`, `withTransaction()`.
|
||||||
|
- `services/db/migrations.ts` — versioned, chained migrations via `PRAGMA user_version` (list of `{ version, up }`).
|
||||||
|
- `services/db/schema.ts` — `DATABASE_NAME`, `DATABASE_VERSION`, column-list constants.
|
||||||
|
- `services/db/repositories/` — one module per table (`folders`, `files`, `user_preferences`).
|
||||||
|
- `services/localStorage.ts` is a thin re-export (`services/db`) kept for legacy imports.
|
||||||
|
- Tables: `folders`, `files`, `user_preferences`; `files.folder_uri` has index `idx_files_folder_uri`.
|
||||||
- Folder and file per-row sync status: `local` | `cloud` | `local-cloud`.
|
- Folder and file per-row sync status: `local` | `cloud` | `local-cloud`.
|
||||||
- `services/localStorage.ts` is a thin re-export of `sqliteStorage` for legacy imports.
|
|
||||||
- Query usage: `getFiles(folderUri?)`, `getFolders()`, `getFolderFolders(parentUri)`, `saveFolder`, `saveFile`, `saveUserPreferences`, `getUserPreferences`, `removeFolder`, `removeFile`.
|
- Query usage: `getFiles(folderUri?)`, `getFolders()`, `getFolderFolders(parentUri)`, `saveFolder`, `saveFile`, `saveUserPreferences`, `getUserPreferences`, `removeFolder`, `removeFile`.
|
||||||
- Heavy processing stays server-side; SQLite only persists local metadata/state.
|
- Heavy processing stays server-side; SQLite only persists local metadata/state.
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import * as SQLite from 'expo-sqlite';
|
||||||
|
import type { SQLiteDatabase } from 'expo-sqlite';
|
||||||
|
import { migrateDatabase } from './migrations';
|
||||||
|
import { DATABASE_NAME } from './schema';
|
||||||
|
|
||||||
|
let database: SQLiteDatabase | null = null;
|
||||||
|
|
||||||
|
export async function getDatabase(): Promise<SQLiteDatabase> {
|
||||||
|
if (database) return database;
|
||||||
|
|
||||||
|
const db = await SQLite.openDatabaseAsync(DATABASE_NAME);
|
||||||
|
await migrateDatabase(db);
|
||||||
|
database = db;
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function closeDatabase(): Promise<void> {
|
||||||
|
if (!database) return;
|
||||||
|
await database.closeAsync();
|
||||||
|
database = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function withTransaction<T>(
|
||||||
|
work: (db: SQLiteDatabase) => Promise<T>,
|
||||||
|
): Promise<T> {
|
||||||
|
const db = await getDatabase();
|
||||||
|
let result!: T;
|
||||||
|
await db.withTransactionAsync(async () => {
|
||||||
|
result = await work(db);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export { getDatabase, closeDatabase, withTransaction } from './client';
|
||||||
|
export * from './repositories';
|
||||||
|
export type { FolderContext, StoredFile, SyncStatus, UserPreferences } from './types';
|
||||||
|
export { DATABASE_NAME, DATABASE_VERSION } from './schema';
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import type { SQLiteDatabase } from 'expo-sqlite';
|
||||||
|
import { DATABASE_VERSION } from './schema';
|
||||||
|
|
||||||
|
type Migration = {
|
||||||
|
version: number;
|
||||||
|
up: (db: SQLiteDatabase) => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MIGRATIONS: Migration[] = [
|
||||||
|
{
|
||||||
|
version: 1,
|
||||||
|
up: async (db) => {
|
||||||
|
await db.execAsync(`
|
||||||
|
PRAGMA journal_mode = 'wal';
|
||||||
|
PRAGMA foreign_keys = ON;
|
||||||
|
CREATE TABLE IF NOT EXISTS folders (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
uri TEXT NOT NULL UNIQUE,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
"exists" INTEGER,
|
||||||
|
sync_status TEXT NOT NULL DEFAULT 'local',
|
||||||
|
added_at INTEGER NOT NULL,
|
||||||
|
parent_uri TEXT
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS files (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
uri TEXT NOT NULL UNIQUE,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
folder_uri TEXT NOT NULL,
|
||||||
|
extension TEXT,
|
||||||
|
size INTEGER,
|
||||||
|
"type" TEXT,
|
||||||
|
"exists" INTEGER,
|
||||||
|
last_modified INTEGER,
|
||||||
|
sync_status TEXT NOT NULL DEFAULT 'local',
|
||||||
|
added_at INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY (folder_uri) REFERENCES folders(uri) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS user_preferences (
|
||||||
|
"key" TEXT PRIMARY KEY NOT NULL,
|
||||||
|
"value" TEXT NOT NULL,
|
||||||
|
updated_at INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: 2,
|
||||||
|
up: async (db) => {
|
||||||
|
await db.execAsync(`
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_files_folder_uri ON files(folder_uri);
|
||||||
|
`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
async function readUserVersion(db: SQLiteDatabase): Promise<number> {
|
||||||
|
const row = await db.getFirstAsync<{ user_version: number }>('PRAGMA user_version');
|
||||||
|
return row?.user_version ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function writeUserVersion(db: SQLiteDatabase, version: number): Promise<void> {
|
||||||
|
await db.execAsync(`PRAGMA user_version = ${version}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function migrateDatabase(
|
||||||
|
db: SQLiteDatabase,
|
||||||
|
targetVersion: number = DATABASE_VERSION,
|
||||||
|
): Promise<void> {
|
||||||
|
let currentVersion = await readUserVersion(db);
|
||||||
|
|
||||||
|
for (const migration of MIGRATIONS) {
|
||||||
|
if (migration.version > currentVersion && migration.version <= targetVersion) {
|
||||||
|
await migration.up(db);
|
||||||
|
await writeUserVersion(db, migration.version);
|
||||||
|
currentVersion = migration.version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { getDatabase } from '../client';
|
||||||
|
import { FILE_COLUMNS_SQL } from '../schema';
|
||||||
|
import type { FileEntry, FileRow, StoredFile } from '../types';
|
||||||
|
|
||||||
|
export async function saveFile(file: FileEntry, folderUri: string): Promise<void> {
|
||||||
|
const db = await getDatabase();
|
||||||
|
const exists = file.exists ? 1 : 0;
|
||||||
|
|
||||||
|
await db.runAsync(
|
||||||
|
`INSERT INTO files (uri, name, folder_uri, extension, size, "type", "exists", last_modified, added_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
ON CONFLICT(uri) DO UPDATE SET
|
||||||
|
name = excluded.name, folder_uri = excluded.folder_uri, extension = excluded.extension,
|
||||||
|
size = excluded.size, "type" = excluded."type", "exists" = excluded."exists",
|
||||||
|
last_modified = excluded.last_modified`,
|
||||||
|
file.uri,
|
||||||
|
file.name,
|
||||||
|
folderUri,
|
||||||
|
file.extension ?? null,
|
||||||
|
file.size,
|
||||||
|
file.type ?? null,
|
||||||
|
exists,
|
||||||
|
file.lastModified,
|
||||||
|
Date.now(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFiles(folderUri?: string): Promise<StoredFile[]> {
|
||||||
|
const db = await getDatabase();
|
||||||
|
const rows = folderUri
|
||||||
|
? await db.getAllAsync<FileRow>(
|
||||||
|
`SELECT ${FILE_COLUMNS_SQL} FROM files WHERE folder_uri = ? ORDER BY name ASC`,
|
||||||
|
folderUri,
|
||||||
|
)
|
||||||
|
: await db.getAllAsync<FileRow>(
|
||||||
|
`SELECT ${FILE_COLUMNS_SQL} FROM files ORDER BY name ASC`,
|
||||||
|
);
|
||||||
|
return rows.map(toStoredFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeFile(uri: string): Promise<void> {
|
||||||
|
const db = await getDatabase();
|
||||||
|
await db.runAsync('DELETE FROM files WHERE uri = ?', uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toStoredFile(row: FileRow): StoredFile {
|
||||||
|
return {
|
||||||
|
uri: row.uri,
|
||||||
|
name: row.name,
|
||||||
|
isDirectory: false,
|
||||||
|
extension: row.extension ?? '',
|
||||||
|
exists: row.exists === 1,
|
||||||
|
size: row.size,
|
||||||
|
type: row.type ?? '',
|
||||||
|
lastModified: row.last_modified,
|
||||||
|
folderUri: row.folder_uri,
|
||||||
|
syncStatus: row.sync_status,
|
||||||
|
addedAt: row.added_at,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { getDatabase } from '../client';
|
||||||
|
import { FOLDER_COLUMNS_SQL } from '../schema';
|
||||||
|
import type { Folder, FolderContext, FolderRow, SyncStatus } from '../types';
|
||||||
|
|
||||||
|
type SaveFolderOptions = {
|
||||||
|
parentUri?: string;
|
||||||
|
syncStatus?: SyncStatus;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function saveFolder(
|
||||||
|
folder: Folder,
|
||||||
|
options?: SaveFolderOptions,
|
||||||
|
): Promise<void> {
|
||||||
|
const db = await getDatabase();
|
||||||
|
const exists = folder.exists === undefined ? null : folder.exists ? 1 : 0;
|
||||||
|
|
||||||
|
await db.runAsync(
|
||||||
|
`INSERT INTO folders (uri, name, "exists", sync_status, added_at, parent_uri)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
ON CONFLICT(uri) DO UPDATE SET
|
||||||
|
name = excluded.name, "exists" = excluded."exists", sync_status = excluded.sync_status`,
|
||||||
|
folder.uri,
|
||||||
|
folder.name,
|
||||||
|
exists,
|
||||||
|
options?.syncStatus ?? 'local',
|
||||||
|
Date.now(),
|
||||||
|
options?.parentUri ?? null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveDirectory(folder: Folder): Promise<boolean> {
|
||||||
|
await saveFolder(folder);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFolders(): Promise<FolderContext[]> {
|
||||||
|
const db = await getDatabase();
|
||||||
|
const rows = await db.getAllAsync<FolderRow>(
|
||||||
|
`SELECT ${FOLDER_COLUMNS_SQL} FROM folders ORDER BY added_at ASC, name ASC`,
|
||||||
|
);
|
||||||
|
return rows.map(toFolderContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFolderFolders(parentUri: string): Promise<FolderContext[]> {
|
||||||
|
const db = await getDatabase();
|
||||||
|
const rows = await db.getAllAsync<FolderRow>(
|
||||||
|
`SELECT ${FOLDER_COLUMNS_SQL} FROM folders WHERE parent_uri = ? ORDER BY name ASC`,
|
||||||
|
parentUri,
|
||||||
|
);
|
||||||
|
return rows.map(toFolderContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeFolder(uri: string): Promise<void> {
|
||||||
|
const db = await getDatabase();
|
||||||
|
await db.runAsync('DELETE FROM folders WHERE uri = ?', uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toFolderContext(row: FolderRow): FolderContext {
|
||||||
|
return {
|
||||||
|
syncStatus: row.sync_status,
|
||||||
|
addedAt: row.added_at,
|
||||||
|
folder: {
|
||||||
|
uri: row.uri,
|
||||||
|
name: row.name,
|
||||||
|
isDirectory: true,
|
||||||
|
exists: row.exists === null ? undefined : row.exists === 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export {
|
||||||
|
saveFolder,
|
||||||
|
saveDirectory,
|
||||||
|
getFolders,
|
||||||
|
getFolderFolders,
|
||||||
|
removeFolder,
|
||||||
|
} from './folders';
|
||||||
|
export { saveFile, getFiles, removeFile } from './files';
|
||||||
|
export { saveUserPreferences, getUserPreferences } from './preferences';
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { getDatabase } from '../client';
|
||||||
|
import { PREFERENCES_KEY } from '../schema';
|
||||||
|
import type { UserPreferences } from '../types';
|
||||||
|
|
||||||
|
const DEFAULT_PREFERENCES: UserPreferences = {
|
||||||
|
syncMode: 'full',
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function saveUserPreferences(preferences: UserPreferences): Promise<void> {
|
||||||
|
const db = await getDatabase();
|
||||||
|
await db.runAsync(
|
||||||
|
`INSERT INTO user_preferences ("key", "value", updated_at) VALUES (?, ?, ?)
|
||||||
|
ON CONFLICT("key") DO UPDATE SET "value" = excluded."value", updated_at = excluded.updated_at`,
|
||||||
|
PREFERENCES_KEY,
|
||||||
|
JSON.stringify(preferences),
|
||||||
|
Date.now(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserPreferences(): Promise<UserPreferences> {
|
||||||
|
const db = await getDatabase();
|
||||||
|
const row = await db.getFirstAsync<{ value: string }>(
|
||||||
|
'SELECT "value" FROM user_preferences WHERE "key" = ?',
|
||||||
|
PREFERENCES_KEY,
|
||||||
|
);
|
||||||
|
if (!row) return DEFAULT_PREFERENCES;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return { ...DEFAULT_PREFERENCES, ...JSON.parse(row.value) } satisfies UserPreferences;
|
||||||
|
} catch {
|
||||||
|
return DEFAULT_PREFERENCES;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
export const DATABASE_NAME = 'dot.db';
|
||||||
|
|
||||||
|
export const DATABASE_VERSION = 2;
|
||||||
|
|
||||||
|
export const PREFERENCES_KEY = 'user_preferences';
|
||||||
|
|
||||||
|
export const FOLDER_COLUMNS = [
|
||||||
|
'uri',
|
||||||
|
'name',
|
||||||
|
'"exists"',
|
||||||
|
'sync_status',
|
||||||
|
'added_at',
|
||||||
|
'parent_uri',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export const FOLDER_COLUMNS_SQL = FOLDER_COLUMNS.join(', ');
|
||||||
|
|
||||||
|
export const FILE_COLUMNS = [
|
||||||
|
'uri',
|
||||||
|
'name',
|
||||||
|
'folder_uri',
|
||||||
|
'extension',
|
||||||
|
'size',
|
||||||
|
'"type"',
|
||||||
|
'"exists"',
|
||||||
|
'last_modified',
|
||||||
|
'sync_status',
|
||||||
|
'added_at',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export const FILE_COLUMNS_SQL = FILE_COLUMNS.join(', ');
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import type { FileEntry, Folder } from "./safDirectory.types";
|
import type { FileEntry, Folder } from '../safDirectory.types';
|
||||||
|
|
||||||
|
export type { FileEntry, Folder } from '../safDirectory.types';
|
||||||
|
|
||||||
export type SyncStatus = 'local' | 'cloud' | 'local-cloud';
|
export type SyncStatus = 'local' | 'cloud' | 'local-cloud';
|
||||||
|
|
||||||
@@ -9,6 +9,6 @@ export {
|
|||||||
saveFolder,
|
saveFolder,
|
||||||
saveDirectory,
|
saveDirectory,
|
||||||
saveUserPreferences,
|
saveUserPreferences,
|
||||||
} from './sqliteStorage';
|
} from './db';
|
||||||
|
|
||||||
export type { FolderContext, StoredFile, SyncStatus, UserPreferences } from './sqliteStorage.types';
|
export type { FolderContext, StoredFile, SyncStatus, UserPreferences } from './db';
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
import * as SQLite from 'expo-sqlite';
|
|
||||||
import type { SQLiteDatabase } from 'expo-sqlite';
|
|
||||||
import type { FileEntry, Folder } from './safDirectory.types';
|
|
||||||
import type { UserPreferences,SyncStatus,FolderContext, FolderRow,StoredFile, FileRow } from './sqliteStorage.types';
|
|
||||||
|
|
||||||
const DATABASE_NAME = 'dot.db';
|
|
||||||
const DATABASE_VERSION = 1;
|
|
||||||
|
|
||||||
const PREFERENCES_KEY = 'user_preferences';
|
|
||||||
|
|
||||||
let database: SQLiteDatabase | null = null;
|
|
||||||
|
|
||||||
const DEFAULT_PREFERENCES: UserPreferences = {
|
|
||||||
syncMode: 'full',
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function getDatabase(): Promise<SQLiteDatabase> {
|
|
||||||
if (database) return database;
|
|
||||||
|
|
||||||
const db = await SQLite.openDatabaseAsync(DATABASE_NAME);
|
|
||||||
await migrateIfNeeded(db);
|
|
||||||
database = db;
|
|
||||||
return db;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function migrateIfNeeded(db: SQLiteDatabase): Promise<void> {
|
|
||||||
const row = await db.getFirstAsync<{ user_version: number }>('PRAGMA user_version');
|
|
||||||
const currentVersion = row?.user_version ?? 0;
|
|
||||||
if (currentVersion >= DATABASE_VERSION) return;
|
|
||||||
|
|
||||||
if (currentVersion === 0) {
|
|
||||||
await db.execAsync(`
|
|
||||||
PRAGMA journal_mode = 'wal';
|
|
||||||
PRAGMA foreign_keys = ON;
|
|
||||||
CREATE TABLE IF NOT EXISTS folders (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
||||||
uri TEXT NOT NULL UNIQUE,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
"exists" INTEGER,
|
|
||||||
sync_status TEXT NOT NULL DEFAULT 'local',
|
|
||||||
added_at INTEGER NOT NULL,
|
|
||||||
parent_uri TEXT
|
|
||||||
);
|
|
||||||
CREATE TABLE IF NOT EXISTS files (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
||||||
uri TEXT NOT NULL UNIQUE,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
folder_uri TEXT NOT NULL,
|
|
||||||
extension TEXT,
|
|
||||||
size INTEGER,
|
|
||||||
"type" TEXT,
|
|
||||||
"exists" INTEGER,
|
|
||||||
last_modified INTEGER,
|
|
||||||
sync_status TEXT NOT NULL DEFAULT 'local',
|
|
||||||
added_at INTEGER NOT NULL,
|
|
||||||
FOREIGN KEY (folder_uri) REFERENCES folders(uri) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
CREATE TABLE IF NOT EXISTS user_preferences (
|
|
||||||
"key" TEXT PRIMARY KEY NOT NULL,
|
|
||||||
"value" TEXT NOT NULL,
|
|
||||||
updated_at INTEGER NOT NULL
|
|
||||||
);
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.execAsync(`PRAGMA user_version = ${DATABASE_VERSION}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function saveFolder(
|
|
||||||
folder: Folder,
|
|
||||||
options?: { parentUri?: string; syncStatus?: SyncStatus },
|
|
||||||
): Promise<void> {
|
|
||||||
const db = await getDatabase();
|
|
||||||
const exists = folder.exists === undefined ? null : folder.exists ? 1 : 0;
|
|
||||||
|
|
||||||
await db.runAsync(
|
|
||||||
`INSERT INTO folders (uri, name, "exists", sync_status, added_at, parent_uri)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
|
||||||
ON CONFLICT(uri) DO UPDATE SET
|
|
||||||
name = excluded.name, "exists" = excluded."exists", sync_status = excluded.sync_status`,
|
|
||||||
folder.uri,
|
|
||||||
folder.name,
|
|
||||||
exists,
|
|
||||||
options?.syncStatus ?? 'local',
|
|
||||||
Date.now(),
|
|
||||||
options?.parentUri ?? null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function saveDirectory(folder: Folder): Promise<boolean> {
|
|
||||||
await saveFolder(folder);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getFolders(): Promise<FolderContext[]> {
|
|
||||||
const db = await getDatabase();
|
|
||||||
const rows = await db.getAllAsync<FolderRow>(
|
|
||||||
'SELECT * FROM folders ORDER BY added_at ASC, name ASC',
|
|
||||||
);
|
|
||||||
return rows.map(toFolderContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getFolderFolders(parentUri: string): Promise<FolderContext[]> {
|
|
||||||
const db = await getDatabase();
|
|
||||||
const rows = await db.getAllAsync<FolderRow>(
|
|
||||||
'SELECT * FROM folders WHERE parent_uri = ? ORDER BY name ASC',
|
|
||||||
parentUri,
|
|
||||||
);
|
|
||||||
return rows.map(toFolderContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function removeFolder(uri: string): Promise<void> {
|
|
||||||
const db = await getDatabase();
|
|
||||||
await db.runAsync('DELETE FROM folders WHERE uri = ?', uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function saveFile(file: FileEntry, folderUri: string): Promise<void> {
|
|
||||||
const db = await getDatabase();
|
|
||||||
const exists = file.exists ? 1 : 0;
|
|
||||||
|
|
||||||
await db.runAsync(
|
|
||||||
`INSERT INTO files (uri, name, folder_uri, extension, size, "type", "exists", last_modified, added_at)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
ON CONFLICT(uri) DO UPDATE SET
|
|
||||||
name = excluded.name, folder_uri = excluded.folder_uri, extension = excluded.extension,
|
|
||||||
size = excluded.size, "type" = excluded."type", "exists" = excluded."exists",
|
|
||||||
last_modified = excluded.last_modified`,
|
|
||||||
file.uri,
|
|
||||||
file.name,
|
|
||||||
folderUri,
|
|
||||||
file.extension ?? null,
|
|
||||||
file.size,
|
|
||||||
file.type ?? null,
|
|
||||||
exists,
|
|
||||||
file.lastModified,
|
|
||||||
Date.now(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getFiles(folderUri?: string): Promise<StoredFile[]> {
|
|
||||||
const db = await getDatabase();
|
|
||||||
const rows = folderUri
|
|
||||||
? await db.getAllAsync<FileRow>(
|
|
||||||
'SELECT * FROM files WHERE folder_uri = ? ORDER BY name ASC',
|
|
||||||
folderUri,
|
|
||||||
)
|
|
||||||
: await db.getAllAsync<FileRow>('SELECT * FROM files ORDER BY name ASC');
|
|
||||||
return rows.map(toStoredFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function removeFile(uri: string): Promise<void> {
|
|
||||||
const db = await getDatabase();
|
|
||||||
await db.runAsync('DELETE FROM files WHERE uri = ?', uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function saveUserPreferences(preferences: UserPreferences): Promise<void> {
|
|
||||||
const db = await getDatabase();
|
|
||||||
await db.runAsync(
|
|
||||||
`INSERT INTO user_preferences ("key", "value", updated_at) VALUES (?, ?, ?)
|
|
||||||
ON CONFLICT("key") DO UPDATE SET "value" = excluded."value", updated_at = excluded.updated_at`,
|
|
||||||
PREFERENCES_KEY,
|
|
||||||
JSON.stringify(preferences),
|
|
||||||
Date.now(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getUserPreferences(): Promise<UserPreferences> {
|
|
||||||
const db = await getDatabase();
|
|
||||||
const row = await db.getFirstAsync<{ value: string }>(
|
|
||||||
'SELECT "value" FROM user_preferences WHERE "key" = ?',
|
|
||||||
PREFERENCES_KEY,
|
|
||||||
);
|
|
||||||
if (!row) return DEFAULT_PREFERENCES;
|
|
||||||
|
|
||||||
try {
|
|
||||||
return { ...DEFAULT_PREFERENCES, ...JSON.parse(row.value) } satisfies UserPreferences;
|
|
||||||
} catch {
|
|
||||||
return DEFAULT_PREFERENCES;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toFolderContext(row: FolderRow): FolderContext {
|
|
||||||
return {
|
|
||||||
syncStatus: row.sync_status,
|
|
||||||
addedAt: row.added_at,
|
|
||||||
folder: {
|
|
||||||
uri: row.uri,
|
|
||||||
name: row.name,
|
|
||||||
isDirectory: true,
|
|
||||||
exists: row.exists === null ? undefined : row.exists === 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function toStoredFile(row: FileRow): StoredFile {
|
|
||||||
return {
|
|
||||||
uri: row.uri,
|
|
||||||
name: row.name,
|
|
||||||
isDirectory: false,
|
|
||||||
extension: row.extension ?? '',
|
|
||||||
exists: row.exists === 1,
|
|
||||||
size: row.size,
|
|
||||||
type: row.type ?? '',
|
|
||||||
lastModified: row.last_modified,
|
|
||||||
folderUri: row.folder_uri,
|
|
||||||
syncStatus: row.sync_status,
|
|
||||||
addedAt: row.added_at,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user