mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-04-09 16:19:25 -07:00
fix(ci): restore stats-server fallback and unblock coverage tests
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import type { DatabaseSync } from './sqlite';
|
||||
import { finalizeSessionRecord } from './session';
|
||||
import { nowMs } from './time';
|
||||
import { toDbMs } from './query-shared';
|
||||
import type { LifetimeRebuildSummary, SessionState } from './types';
|
||||
|
||||
interface TelemetryRow {
|
||||
@@ -19,11 +20,12 @@ interface AnimeRow {
|
||||
episodes_total: number | null;
|
||||
}
|
||||
|
||||
function asPositiveNumber(value: number | null, fallback: number): number {
|
||||
if (value === null || !Number.isFinite(value)) {
|
||||
function asPositiveNumber(value: number | string | null, fallback: number): number {
|
||||
const numericValue = typeof value === 'number' ? value : Number(value);
|
||||
if (!Number.isFinite(numericValue)) {
|
||||
return fallback;
|
||||
}
|
||||
return Math.max(0, Math.floor(value));
|
||||
return Math.max(0, Math.floor(numericValue));
|
||||
}
|
||||
|
||||
interface ExistenceRow {
|
||||
@@ -41,22 +43,22 @@ interface LifetimeAnimeStateRow {
|
||||
interface RetainedSessionRow {
|
||||
sessionId: number;
|
||||
videoId: number;
|
||||
startedAtMs: number;
|
||||
endedAtMs: number;
|
||||
lastMediaMs: number | null;
|
||||
totalWatchedMs: number;
|
||||
activeWatchedMs: number;
|
||||
linesSeen: number;
|
||||
tokensSeen: number;
|
||||
cardsMined: number;
|
||||
lookupCount: number;
|
||||
lookupHits: number;
|
||||
yomitanLookupCount: number;
|
||||
pauseCount: number;
|
||||
pauseMs: number;
|
||||
seekForwardCount: number;
|
||||
seekBackwardCount: number;
|
||||
mediaBufferEvents: number;
|
||||
startedAtMs: number | string;
|
||||
endedAtMs: number | string;
|
||||
lastMediaMs: number | string | null;
|
||||
totalWatchedMs: number | string;
|
||||
activeWatchedMs: number | string;
|
||||
linesSeen: number | string;
|
||||
tokensSeen: number | string;
|
||||
cardsMined: number | string;
|
||||
lookupCount: number | string;
|
||||
lookupHits: number | string;
|
||||
yomitanLookupCount: number | string;
|
||||
pauseCount: number | string;
|
||||
pauseMs: number | string;
|
||||
seekForwardCount: number | string;
|
||||
seekBackwardCount: number | string;
|
||||
mediaBufferEvents: number | string;
|
||||
}
|
||||
|
||||
function hasRetainedPriorSession(
|
||||
@@ -110,7 +112,7 @@ function isFirstSessionForLocalDay(
|
||||
);
|
||||
}
|
||||
|
||||
function resetLifetimeSummaries(db: DatabaseSync, nowMs: number): void {
|
||||
function resetLifetimeSummaries(db: DatabaseSync, nowMs: string): void {
|
||||
db.exec(`
|
||||
DELETE FROM imm_lifetime_anime;
|
||||
DELETE FROM imm_lifetime_media;
|
||||
@@ -136,7 +138,7 @@ function resetLifetimeSummaries(db: DatabaseSync, nowMs: number): void {
|
||||
|
||||
function rebuildLifetimeSummariesInternal(
|
||||
db: DatabaseSync,
|
||||
rebuiltAtMs: number,
|
||||
rebuiltAtMs: string,
|
||||
): LifetimeRebuildSummary {
|
||||
const sessions = db
|
||||
.prepare(
|
||||
@@ -178,30 +180,33 @@ function rebuildLifetimeSummariesInternal(
|
||||
}
|
||||
|
||||
function toRebuildSessionState(row: RetainedSessionRow): SessionState {
|
||||
const startedAtMs = Number(row.startedAtMs);
|
||||
const endedAtMs = Number(row.endedAtMs);
|
||||
const lastMediaMs = row.lastMediaMs === null ? null : Number(row.lastMediaMs);
|
||||
return {
|
||||
sessionId: row.sessionId,
|
||||
videoId: row.videoId,
|
||||
startedAtMs: row.startedAtMs,
|
||||
startedAtMs,
|
||||
currentLineIndex: 0,
|
||||
lastWallClockMs: row.endedAtMs,
|
||||
lastMediaMs: row.lastMediaMs,
|
||||
lastWallClockMs: endedAtMs,
|
||||
lastMediaMs,
|
||||
lastPauseStartMs: null,
|
||||
isPaused: false,
|
||||
pendingTelemetry: false,
|
||||
markedWatched: false,
|
||||
totalWatchedMs: Math.max(0, row.totalWatchedMs),
|
||||
activeWatchedMs: Math.max(0, row.activeWatchedMs),
|
||||
linesSeen: Math.max(0, row.linesSeen),
|
||||
tokensSeen: Math.max(0, row.tokensSeen),
|
||||
cardsMined: Math.max(0, row.cardsMined),
|
||||
lookupCount: Math.max(0, row.lookupCount),
|
||||
lookupHits: Math.max(0, row.lookupHits),
|
||||
yomitanLookupCount: Math.max(0, row.yomitanLookupCount),
|
||||
pauseCount: Math.max(0, row.pauseCount),
|
||||
pauseMs: Math.max(0, row.pauseMs),
|
||||
seekForwardCount: Math.max(0, row.seekForwardCount),
|
||||
seekBackwardCount: Math.max(0, row.seekBackwardCount),
|
||||
mediaBufferEvents: Math.max(0, row.mediaBufferEvents),
|
||||
totalWatchedMs: asPositiveNumber(row.totalWatchedMs, 0),
|
||||
activeWatchedMs: asPositiveNumber(row.activeWatchedMs, 0),
|
||||
linesSeen: asPositiveNumber(row.linesSeen, 0),
|
||||
tokensSeen: asPositiveNumber(row.tokensSeen, 0),
|
||||
cardsMined: asPositiveNumber(row.cardsMined, 0),
|
||||
lookupCount: asPositiveNumber(row.lookupCount, 0),
|
||||
lookupHits: asPositiveNumber(row.lookupHits, 0),
|
||||
yomitanLookupCount: asPositiveNumber(row.yomitanLookupCount, 0),
|
||||
pauseCount: asPositiveNumber(row.pauseCount, 0),
|
||||
pauseMs: asPositiveNumber(row.pauseMs, 0),
|
||||
seekForwardCount: asPositiveNumber(row.seekForwardCount, 0),
|
||||
seekBackwardCount: asPositiveNumber(row.seekBackwardCount, 0),
|
||||
mediaBufferEvents: asPositiveNumber(row.mediaBufferEvents, 0),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -247,14 +252,14 @@ function getRetainedStaleActiveSessions(db: DatabaseSync): RetainedSessionRow[]
|
||||
function upsertLifetimeMedia(
|
||||
db: DatabaseSync,
|
||||
videoId: number,
|
||||
nowMs: number,
|
||||
nowMs: string,
|
||||
activeMs: number,
|
||||
cardsMined: number,
|
||||
linesSeen: number,
|
||||
tokensSeen: number,
|
||||
completed: number,
|
||||
startedAtMs: number,
|
||||
endedAtMs: number,
|
||||
startedAtMs: number | string,
|
||||
endedAtMs: number | string,
|
||||
): void {
|
||||
db.prepare(
|
||||
`
|
||||
@@ -310,15 +315,15 @@ function upsertLifetimeMedia(
|
||||
function upsertLifetimeAnime(
|
||||
db: DatabaseSync,
|
||||
animeId: number,
|
||||
nowMs: number,
|
||||
nowMs: string,
|
||||
activeMs: number,
|
||||
cardsMined: number,
|
||||
linesSeen: number,
|
||||
tokensSeen: number,
|
||||
episodesStartedDelta: number,
|
||||
episodesCompletedDelta: number,
|
||||
startedAtMs: number,
|
||||
endedAtMs: number,
|
||||
startedAtMs: number | string,
|
||||
endedAtMs: number | string,
|
||||
): void {
|
||||
db.prepare(
|
||||
`
|
||||
@@ -377,7 +382,7 @@ function upsertLifetimeAnime(
|
||||
export function applySessionLifetimeSummary(
|
||||
db: DatabaseSync,
|
||||
session: SessionState,
|
||||
endedAtMs: number,
|
||||
endedAtMs: number | string,
|
||||
): void {
|
||||
const applyResult = db
|
||||
.prepare(
|
||||
@@ -392,8 +397,8 @@ export function applySessionLifetimeSummary(
|
||||
)
|
||||
ON CONFLICT(session_id) DO NOTHING
|
||||
`,
|
||||
)
|
||||
.run(session.sessionId, endedAtMs, nowMs(), nowMs());
|
||||
)
|
||||
.run(session.sessionId, toDbMs(endedAtMs), toDbMs(nowMs()), toDbMs(nowMs()));
|
||||
|
||||
if ((applyResult.changes ?? 0) <= 0) {
|
||||
return;
|
||||
@@ -468,7 +473,7 @@ export function applySessionLifetimeSummary(
|
||||
? 1
|
||||
: 0;
|
||||
|
||||
const updatedAtMs = nowMs();
|
||||
const updatedAtMs = toDbMs(nowMs());
|
||||
db.prepare(
|
||||
`
|
||||
UPDATE imm_lifetime_global
|
||||
@@ -524,7 +529,7 @@ export function applySessionLifetimeSummary(
|
||||
}
|
||||
|
||||
export function rebuildLifetimeSummaries(db: DatabaseSync): LifetimeRebuildSummary {
|
||||
const rebuiltAtMs = nowMs();
|
||||
const rebuiltAtMs = toDbMs(nowMs());
|
||||
db.exec('BEGIN');
|
||||
try {
|
||||
const summary = rebuildLifetimeSummariesInTransaction(db, rebuiltAtMs);
|
||||
@@ -538,7 +543,7 @@ export function rebuildLifetimeSummaries(db: DatabaseSync): LifetimeRebuildSumma
|
||||
|
||||
export function rebuildLifetimeSummariesInTransaction(
|
||||
db: DatabaseSync,
|
||||
rebuiltAtMs = nowMs(),
|
||||
rebuiltAtMs = toDbMs(nowMs()),
|
||||
): LifetimeRebuildSummary {
|
||||
return rebuildLifetimeSummariesInternal(db, rebuiltAtMs);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ const DAILY_MS = 86_400_000;
|
||||
const ZERO_ID = 0;
|
||||
|
||||
interface RollupStateRow {
|
||||
state_value: number;
|
||||
state_value: string;
|
||||
}
|
||||
|
||||
interface RollupGroupRow {
|
||||
@@ -125,7 +125,7 @@ function setLastRollupSampleMs(db: DatabaseSync, sampleMs: number | bigint): voi
|
||||
`INSERT INTO imm_rollup_state (state_key, state_value)
|
||||
VALUES (?, ?)
|
||||
ON CONFLICT(state_key) DO UPDATE SET state_value = excluded.state_value`,
|
||||
).run(ROLLUP_STATE_KEY, sampleMs);
|
||||
).run(ROLLUP_STATE_KEY, toDbMs(sampleMs));
|
||||
}
|
||||
|
||||
function resetRollups(db: DatabaseSync): void {
|
||||
|
||||
@@ -271,6 +271,19 @@ export function deleteSessionsByIds(db: DatabaseSync, sessionIds: number[]): voi
|
||||
db.prepare(`DELETE FROM imm_sessions WHERE session_id IN (${placeholders})`).run(...sessionIds);
|
||||
}
|
||||
|
||||
export function toDbMs(ms: number | bigint): bigint {
|
||||
return BigInt(Math.trunc(Number(ms)));
|
||||
export function toDbMs(ms: number | bigint | string): string {
|
||||
if (typeof ms === 'bigint') {
|
||||
return ms.toString();
|
||||
}
|
||||
if (typeof ms === 'string') {
|
||||
const parsedMs = Number(ms);
|
||||
if (!Number.isFinite(parsedMs)) {
|
||||
return '0';
|
||||
}
|
||||
return String(Math.trunc(parsedMs));
|
||||
}
|
||||
if (!Number.isFinite(ms)) {
|
||||
return '0';
|
||||
}
|
||||
return String(Math.trunc(ms));
|
||||
}
|
||||
|
||||
@@ -287,9 +287,9 @@ function ensureLifetimeSummaryTables(db: DatabaseSync): void {
|
||||
episodes_started INTEGER NOT NULL DEFAULT 0,
|
||||
episodes_completed INTEGER NOT NULL DEFAULT 0,
|
||||
anime_completed INTEGER NOT NULL DEFAULT 0,
|
||||
last_rebuilt_ms INTEGER,
|
||||
CREATED_DATE INTEGER,
|
||||
LAST_UPDATE_DATE INTEGER
|
||||
last_rebuilt_ms TEXT,
|
||||
CREATED_DATE TEXT,
|
||||
LAST_UPDATE_DATE TEXT
|
||||
)
|
||||
`);
|
||||
|
||||
@@ -332,10 +332,10 @@ function ensureLifetimeSummaryTables(db: DatabaseSync): void {
|
||||
total_tokens_seen INTEGER NOT NULL DEFAULT 0,
|
||||
episodes_started INTEGER NOT NULL DEFAULT 0,
|
||||
episodes_completed INTEGER NOT NULL DEFAULT 0,
|
||||
first_watched_ms INTEGER,
|
||||
last_watched_ms INTEGER,
|
||||
CREATED_DATE INTEGER,
|
||||
LAST_UPDATE_DATE INTEGER,
|
||||
first_watched_ms TEXT,
|
||||
last_watched_ms TEXT,
|
||||
CREATED_DATE TEXT,
|
||||
LAST_UPDATE_DATE TEXT,
|
||||
FOREIGN KEY(anime_id) REFERENCES imm_anime(anime_id) ON DELETE CASCADE
|
||||
)
|
||||
`);
|
||||
@@ -349,10 +349,10 @@ function ensureLifetimeSummaryTables(db: DatabaseSync): void {
|
||||
total_lines_seen INTEGER NOT NULL DEFAULT 0,
|
||||
total_tokens_seen INTEGER NOT NULL DEFAULT 0,
|
||||
completed INTEGER NOT NULL DEFAULT 0,
|
||||
first_watched_ms INTEGER,
|
||||
last_watched_ms INTEGER,
|
||||
CREATED_DATE INTEGER,
|
||||
LAST_UPDATE_DATE INTEGER,
|
||||
first_watched_ms TEXT,
|
||||
last_watched_ms TEXT,
|
||||
CREATED_DATE TEXT,
|
||||
LAST_UPDATE_DATE TEXT,
|
||||
FOREIGN KEY(video_id) REFERENCES imm_videos(video_id) ON DELETE CASCADE
|
||||
)
|
||||
`);
|
||||
@@ -360,9 +360,9 @@ function ensureLifetimeSummaryTables(db: DatabaseSync): void {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS imm_lifetime_applied_sessions(
|
||||
session_id INTEGER PRIMARY KEY,
|
||||
applied_at_ms INTEGER NOT NULL,
|
||||
CREATED_DATE INTEGER,
|
||||
LAST_UPDATE_DATE INTEGER,
|
||||
applied_at_ms TEXT NOT NULL,
|
||||
CREATED_DATE TEXT,
|
||||
LAST_UPDATE_DATE TEXT,
|
||||
FOREIGN KEY(session_id) REFERENCES imm_sessions(session_id) ON DELETE CASCADE
|
||||
)
|
||||
`);
|
||||
@@ -562,18 +562,18 @@ export function ensureSchema(db: DatabaseSync): void {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS imm_schema_version (
|
||||
schema_version INTEGER PRIMARY KEY,
|
||||
applied_at_ms INTEGER NOT NULL
|
||||
applied_at_ms TEXT NOT NULL
|
||||
);
|
||||
`);
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS imm_rollup_state(
|
||||
state_key TEXT PRIMARY KEY,
|
||||
state_value INTEGER NOT NULL
|
||||
state_value TEXT NOT NULL
|
||||
);
|
||||
`);
|
||||
db.exec(`
|
||||
INSERT INTO imm_rollup_state(state_key, state_value)
|
||||
VALUES ('last_rollup_sample_ms', 0)
|
||||
VALUES ('last_rollup_sample_ms', '0')
|
||||
ON CONFLICT(state_key) DO NOTHING
|
||||
`);
|
||||
|
||||
@@ -597,8 +597,8 @@ export function ensureSchema(db: DatabaseSync): void {
|
||||
episodes_total INTEGER,
|
||||
description TEXT,
|
||||
metadata_json TEXT,
|
||||
CREATED_DATE INTEGER,
|
||||
LAST_UPDATE_DATE INTEGER
|
||||
CREATED_DATE TEXT,
|
||||
LAST_UPDATE_DATE TEXT
|
||||
);
|
||||
`);
|
||||
db.exec(`
|
||||
@@ -625,8 +625,8 @@ export function ensureSchema(db: DatabaseSync): void {
|
||||
bitrate_kbps INTEGER, audio_codec_id INTEGER,
|
||||
hash_sha256 TEXT, screenshot_path TEXT,
|
||||
metadata_json TEXT,
|
||||
CREATED_DATE INTEGER,
|
||||
LAST_UPDATE_DATE INTEGER,
|
||||
CREATED_DATE TEXT,
|
||||
LAST_UPDATE_DATE TEXT,
|
||||
FOREIGN KEY(anime_id) REFERENCES imm_anime(anime_id) ON DELETE SET NULL
|
||||
);
|
||||
`);
|
||||
@@ -635,7 +635,8 @@ export function ensureSchema(db: DatabaseSync): void {
|
||||
session_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_uuid TEXT NOT NULL UNIQUE,
|
||||
video_id INTEGER NOT NULL,
|
||||
started_at_ms INTEGER NOT NULL, ended_at_ms INTEGER,
|
||||
started_at_ms TEXT NOT NULL,
|
||||
ended_at_ms TEXT,
|
||||
status INTEGER NOT NULL,
|
||||
locale_id INTEGER, target_lang_id INTEGER,
|
||||
difficulty_tier INTEGER, subtitle_mode INTEGER,
|
||||
@@ -653,8 +654,8 @@ export function ensureSchema(db: DatabaseSync): void {
|
||||
seek_forward_count INTEGER NOT NULL DEFAULT 0,
|
||||
seek_backward_count INTEGER NOT NULL DEFAULT 0,
|
||||
media_buffer_events INTEGER NOT NULL DEFAULT 0,
|
||||
CREATED_DATE INTEGER,
|
||||
LAST_UPDATE_DATE INTEGER,
|
||||
CREATED_DATE TEXT,
|
||||
LAST_UPDATE_DATE TEXT,
|
||||
FOREIGN KEY(video_id) REFERENCES imm_videos(video_id)
|
||||
);
|
||||
`);
|
||||
@@ -662,7 +663,7 @@ export function ensureSchema(db: DatabaseSync): void {
|
||||
CREATE TABLE IF NOT EXISTS imm_session_telemetry(
|
||||
telemetry_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id INTEGER NOT NULL,
|
||||
sample_ms INTEGER NOT NULL,
|
||||
sample_ms TEXT NOT NULL,
|
||||
total_watched_ms INTEGER NOT NULL DEFAULT 0,
|
||||
active_watched_ms INTEGER NOT NULL DEFAULT 0,
|
||||
lines_seen INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -676,8 +677,8 @@ export function ensureSchema(db: DatabaseSync): void {
|
||||
seek_forward_count INTEGER NOT NULL DEFAULT 0,
|
||||
seek_backward_count INTEGER NOT NULL DEFAULT 0,
|
||||
media_buffer_events INTEGER NOT NULL DEFAULT 0,
|
||||
CREATED_DATE INTEGER,
|
||||
LAST_UPDATE_DATE INTEGER,
|
||||
CREATED_DATE TEXT,
|
||||
LAST_UPDATE_DATE TEXT,
|
||||
FOREIGN KEY(session_id) REFERENCES imm_sessions(session_id) ON DELETE CASCADE
|
||||
);
|
||||
`);
|
||||
@@ -685,7 +686,7 @@ export function ensureSchema(db: DatabaseSync): void {
|
||||
CREATE TABLE IF NOT EXISTS imm_session_events(
|
||||
event_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id INTEGER NOT NULL,
|
||||
ts_ms INTEGER NOT NULL,
|
||||
ts_ms TEXT NOT NULL,
|
||||
event_type INTEGER NOT NULL,
|
||||
line_index INTEGER,
|
||||
segment_start_ms INTEGER,
|
||||
@@ -693,8 +694,8 @@ export function ensureSchema(db: DatabaseSync): void {
|
||||
tokens_delta INTEGER NOT NULL DEFAULT 0,
|
||||
cards_delta INTEGER NOT NULL DEFAULT 0,
|
||||
payload_json TEXT,
|
||||
CREATED_DATE INTEGER,
|
||||
LAST_UPDATE_DATE INTEGER,
|
||||
CREATED_DATE TEXT,
|
||||
LAST_UPDATE_DATE TEXT,
|
||||
FOREIGN KEY(session_id) REFERENCES imm_sessions(session_id) ON DELETE CASCADE
|
||||
);
|
||||
`);
|
||||
@@ -710,8 +711,8 @@ export function ensureSchema(db: DatabaseSync): void {
|
||||
cards_per_hour REAL,
|
||||
tokens_per_min REAL,
|
||||
lookup_hit_rate REAL,
|
||||
CREATED_DATE INTEGER,
|
||||
LAST_UPDATE_DATE INTEGER,
|
||||
CREATED_DATE TEXT,
|
||||
LAST_UPDATE_DATE TEXT,
|
||||
PRIMARY KEY (rollup_day, video_id)
|
||||
);
|
||||
`);
|
||||
@@ -724,8 +725,8 @@ export function ensureSchema(db: DatabaseSync): void {
|
||||
total_lines_seen INTEGER NOT NULL DEFAULT 0,
|
||||
total_tokens_seen INTEGER NOT NULL DEFAULT 0,
|
||||
total_cards INTEGER NOT NULL DEFAULT 0,
|
||||
CREATED_DATE INTEGER,
|
||||
LAST_UPDATE_DATE INTEGER,
|
||||
CREATED_DATE TEXT,
|
||||
LAST_UPDATE_DATE TEXT,
|
||||
PRIMARY KEY (rollup_month, video_id)
|
||||
);
|
||||
`);
|
||||
@@ -768,8 +769,8 @@ export function ensureSchema(db: DatabaseSync): void {
|
||||
segment_end_ms INTEGER,
|
||||
text TEXT NOT NULL,
|
||||
secondary_text TEXT,
|
||||
CREATED_DATE INTEGER,
|
||||
LAST_UPDATE_DATE INTEGER,
|
||||
CREATED_DATE TEXT,
|
||||
LAST_UPDATE_DATE TEXT,
|
||||
FOREIGN KEY(session_id) REFERENCES imm_sessions(session_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(event_id) REFERENCES imm_session_events(event_id) ON DELETE SET NULL,
|
||||
FOREIGN KEY(video_id) REFERENCES imm_videos(video_id) ON DELETE CASCADE,
|
||||
@@ -806,9 +807,9 @@ export function ensureSchema(db: DatabaseSync): void {
|
||||
title_romaji TEXT,
|
||||
title_english TEXT,
|
||||
episodes_total INTEGER,
|
||||
fetched_at_ms INTEGER NOT NULL,
|
||||
CREATED_DATE INTEGER,
|
||||
LAST_UPDATE_DATE INTEGER,
|
||||
fetched_at_ms TEXT NOT NULL,
|
||||
CREATED_DATE TEXT,
|
||||
LAST_UPDATE_DATE TEXT,
|
||||
FOREIGN KEY(video_id) REFERENCES imm_videos(video_id) ON DELETE CASCADE
|
||||
);
|
||||
`);
|
||||
@@ -827,9 +828,9 @@ export function ensureSchema(db: DatabaseSync): void {
|
||||
uploader_url TEXT,
|
||||
description TEXT,
|
||||
metadata_json TEXT,
|
||||
fetched_at_ms INTEGER NOT NULL,
|
||||
CREATED_DATE INTEGER,
|
||||
LAST_UPDATE_DATE INTEGER,
|
||||
fetched_at_ms TEXT NOT NULL,
|
||||
CREATED_DATE TEXT,
|
||||
LAST_UPDATE_DATE TEXT,
|
||||
FOREIGN KEY(video_id) REFERENCES imm_videos(video_id) ON DELETE CASCADE
|
||||
);
|
||||
`);
|
||||
@@ -837,24 +838,24 @@ export function ensureSchema(db: DatabaseSync): void {
|
||||
CREATE TABLE IF NOT EXISTS imm_cover_art_blobs(
|
||||
blob_hash TEXT PRIMARY KEY,
|
||||
cover_blob BLOB NOT NULL,
|
||||
CREATED_DATE INTEGER,
|
||||
LAST_UPDATE_DATE INTEGER
|
||||
CREATED_DATE TEXT,
|
||||
LAST_UPDATE_DATE TEXT
|
||||
);
|
||||
`);
|
||||
|
||||
if (currentVersion?.schema_version === 1) {
|
||||
addColumnIfMissing(db, 'imm_videos', 'CREATED_DATE');
|
||||
addColumnIfMissing(db, 'imm_videos', 'LAST_UPDATE_DATE');
|
||||
addColumnIfMissing(db, 'imm_sessions', 'CREATED_DATE');
|
||||
addColumnIfMissing(db, 'imm_sessions', 'LAST_UPDATE_DATE');
|
||||
addColumnIfMissing(db, 'imm_session_telemetry', 'CREATED_DATE');
|
||||
addColumnIfMissing(db, 'imm_session_telemetry', 'LAST_UPDATE_DATE');
|
||||
addColumnIfMissing(db, 'imm_session_events', 'CREATED_DATE');
|
||||
addColumnIfMissing(db, 'imm_session_events', 'LAST_UPDATE_DATE');
|
||||
addColumnIfMissing(db, 'imm_daily_rollups', 'CREATED_DATE');
|
||||
addColumnIfMissing(db, 'imm_daily_rollups', 'LAST_UPDATE_DATE');
|
||||
addColumnIfMissing(db, 'imm_monthly_rollups', 'CREATED_DATE');
|
||||
addColumnIfMissing(db, 'imm_monthly_rollups', 'LAST_UPDATE_DATE');
|
||||
addColumnIfMissing(db, 'imm_videos', 'CREATED_DATE', 'TEXT');
|
||||
addColumnIfMissing(db, 'imm_videos', 'LAST_UPDATE_DATE', 'TEXT');
|
||||
addColumnIfMissing(db, 'imm_sessions', 'CREATED_DATE', 'TEXT');
|
||||
addColumnIfMissing(db, 'imm_sessions', 'LAST_UPDATE_DATE', 'TEXT');
|
||||
addColumnIfMissing(db, 'imm_session_telemetry', 'CREATED_DATE', 'TEXT');
|
||||
addColumnIfMissing(db, 'imm_session_telemetry', 'LAST_UPDATE_DATE', 'TEXT');
|
||||
addColumnIfMissing(db, 'imm_session_events', 'CREATED_DATE', 'TEXT');
|
||||
addColumnIfMissing(db, 'imm_session_events', 'LAST_UPDATE_DATE', 'TEXT');
|
||||
addColumnIfMissing(db, 'imm_daily_rollups', 'CREATED_DATE', 'TEXT');
|
||||
addColumnIfMissing(db, 'imm_daily_rollups', 'LAST_UPDATE_DATE', 'TEXT');
|
||||
addColumnIfMissing(db, 'imm_monthly_rollups', 'CREATED_DATE', 'TEXT');
|
||||
addColumnIfMissing(db, 'imm_monthly_rollups', 'LAST_UPDATE_DATE', 'TEXT');
|
||||
|
||||
const migratedAtMs = toDbMs(nowMs());
|
||||
db.prepare(
|
||||
@@ -938,8 +939,8 @@ export function ensureSchema(db: DatabaseSync): void {
|
||||
segment_end_ms INTEGER,
|
||||
text TEXT NOT NULL,
|
||||
secondary_text TEXT,
|
||||
CREATED_DATE INTEGER,
|
||||
LAST_UPDATE_DATE INTEGER,
|
||||
CREATED_DATE TEXT,
|
||||
LAST_UPDATE_DATE TEXT,
|
||||
FOREIGN KEY(session_id) REFERENCES imm_sessions(session_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(event_id) REFERENCES imm_session_events(event_id) ON DELETE SET NULL,
|
||||
FOREIGN KEY(video_id) REFERENCES imm_videos(video_id) ON DELETE CASCADE,
|
||||
@@ -1088,8 +1089,8 @@ export function ensureSchema(db: DatabaseSync): void {
|
||||
CREATE TABLE IF NOT EXISTS imm_cover_art_blobs(
|
||||
blob_hash TEXT PRIMARY KEY,
|
||||
cover_blob BLOB NOT NULL,
|
||||
CREATED_DATE INTEGER,
|
||||
LAST_UPDATE_DATE INTEGER
|
||||
CREATED_DATE TEXT,
|
||||
LAST_UPDATE_DATE TEXT
|
||||
)
|
||||
`);
|
||||
deduplicateExistingCoverArtRows(db);
|
||||
@@ -1237,7 +1238,7 @@ export function ensureSchema(db: DatabaseSync): void {
|
||||
db.exec('DELETE FROM imm_daily_rollups');
|
||||
db.exec('DELETE FROM imm_monthly_rollups');
|
||||
db.exec(
|
||||
`UPDATE imm_rollup_state SET state_value = 0 WHERE state_key = 'last_rollup_sample_ms'`,
|
||||
`UPDATE imm_rollup_state SET state_value = '0' WHERE state_key = 'last_rollup_sample_ms'`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Hono } from 'hono';
|
||||
import type { ImmersionTrackerService } from './immersion-tracker-service.js';
|
||||
import http from 'node:http';
|
||||
import { basename, extname, resolve, sep } from 'node:path';
|
||||
import { readFileSync, existsSync, statSync } from 'node:fs';
|
||||
import { Readable } from 'node:stream';
|
||||
import { MediaGenerator } from '../../media-generator.js';
|
||||
import { AnkiConnectClient } from '../../anki-connect.js';
|
||||
import type { AnkiConnectConfig } from '../../types.js';
|
||||
@@ -1006,27 +1008,76 @@ export function startStatsServer(config: StatsServerConfig): { close: () => void
|
||||
resolveAnkiNoteId: config.resolveAnkiNoteId,
|
||||
});
|
||||
|
||||
const bunServe = (
|
||||
globalThis as typeof globalThis & {
|
||||
Bun: {
|
||||
serve: (options: {
|
||||
fetch: (typeof app)['fetch'];
|
||||
port: number;
|
||||
hostname: string;
|
||||
}) => { stop: () => void };
|
||||
};
|
||||
}
|
||||
).Bun.serve;
|
||||
const bunServe =
|
||||
(
|
||||
globalThis as typeof globalThis & {
|
||||
Bun?: {
|
||||
serve?: (options: {
|
||||
fetch: (typeof app)['fetch'];
|
||||
port: number;
|
||||
hostname: string;
|
||||
}) => { stop: () => void };
|
||||
};
|
||||
}
|
||||
).Bun?.serve;
|
||||
|
||||
const server = bunServe({
|
||||
fetch: app.fetch,
|
||||
port: config.port,
|
||||
hostname: '127.0.0.1',
|
||||
if (typeof bunServe === 'function') {
|
||||
const server = bunServe({
|
||||
fetch: app.fetch,
|
||||
port: config.port,
|
||||
hostname: '127.0.0.1',
|
||||
});
|
||||
|
||||
return {
|
||||
close: () => {
|
||||
server.stop();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const server = http.createServer(async (req, res) => {
|
||||
const url = new URL(`http://127.0.0.1:${config.port}${req.url}`);
|
||||
const headers = new Headers();
|
||||
for (const [name, value] of Object.entries(req.headers)) {
|
||||
if (value === undefined) continue;
|
||||
if (Array.isArray(value)) {
|
||||
for (const entry of value) {
|
||||
headers.append(name, entry);
|
||||
}
|
||||
} else {
|
||||
headers.set(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
const body = req.method === 'GET' || req.method === 'HEAD' ? undefined : Readable.toWeb(req);
|
||||
|
||||
const response = await app.fetch(
|
||||
new Request(url.toString(), {
|
||||
method: req.method,
|
||||
headers,
|
||||
body,
|
||||
}),
|
||||
);
|
||||
|
||||
res.statusCode = response.status;
|
||||
for (const [name, value] of response.headers) {
|
||||
res.setHeader(name, value);
|
||||
}
|
||||
|
||||
const responseBody = await response.arrayBuffer();
|
||||
if (responseBody.byteLength > 0) {
|
||||
res.end(Buffer.from(responseBody));
|
||||
return;
|
||||
}
|
||||
|
||||
res.end();
|
||||
});
|
||||
|
||||
server.listen(config.port, '127.0.0.1');
|
||||
|
||||
return {
|
||||
close: () => {
|
||||
server.stop();
|
||||
server.close();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user