Update source code and remove runtime data from tracking

Add gitignore rules for browser session data, SQLite databases,
media downloads, and build artifacts. Remove previously tracked
runtime files from the repository.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
jjbruntonandClaude Opus 4.6 committed 2026-05-20 18:54:07 +01:00
1 parent e90213d1ff
commit dfa0580859
23 files changed
+5324 -347

No files matched your search

+119
View File
@@ -0,0 +1,119 @@
import type { Logger } from '../utils/logger.js';
import type { ChatService } from './chat.js';
export interface MediaSyncStatus {
isSyncing: boolean;
lastSyncAt: number | null;
lastSyncDurationMs: number | null;
lastSyncChats: number | null;
lastError: string | null;
nextSyncAt: number | null;
}
export class MediaSyncService {
private syncing = false;
private lastSyncAt: number | null = null;
private lastSyncDurationMs: number | null = null;
private lastSyncChats: number | null = null;
private lastError: string | null = null;
private intervalHandle: NodeJS.Timeout | null = null;
private readonly intervalMs: number;
constructor(
private chatService: ChatService,
private logger: Logger,
intervalMs: number = 30 * 60 * 1000 // default 30 minutes
) {
this.intervalMs = intervalMs;
}
/**
* Start periodic background syncing.
*/
start(): void {
if (this.intervalHandle) return;
this.logger.info(`Media sync: starting background sync every ${Math.round(this.intervalMs / 60000)} min`);
// Run first sync after a short delay to let the app settle
const initialDelay = setTimeout(() => this.runSync(), 15000);
this.intervalHandle = setInterval(() => this.runSync(), this.intervalMs);
// Ensure the initial timeout is cleaned up if stop() is called early
const origStop = this.stop.bind(this);
this.stop = () => {
clearTimeout(initialDelay);
origStop();
};
}
/**
* Stop periodic syncing.
*/
stop(): void {
if (this.intervalHandle) {
clearInterval(this.intervalHandle);
this.intervalHandle = null;
this.logger.info('Media sync: background sync stopped');
}
}
/**
* Trigger a sync manually (e.g. from the dashboard).
* Returns immediately if a sync is already in progress.
*/
async triggerSync(full = false): Promise<boolean> {
if (this.syncing) {
this.logger.info('Media sync: sync already in progress, skipping trigger');
return false;
}
await this.runSync(full);
return true;
}
getStatus(): MediaSyncStatus {
return {
isSyncing: this.syncing,
lastSyncAt: this.lastSyncAt,
lastSyncDurationMs: this.lastSyncDurationMs,
lastSyncChats: this.lastSyncChats,
lastError: this.lastError,
nextSyncAt: this.intervalHandle && this.lastSyncAt
? this.lastSyncAt + this.intervalMs
: null,
};
}
private async runSync(full = false): Promise<void> {
if (this.syncing) return;
this.syncing = true;
this.lastError = null;
const start = Date.now();
try {
if (full) {
this.logger.info('Media sync: running full sync (all conversations)...');
const chats = await this.chatService.fetchAllMessages();
this.lastSyncChats = chats.length;
} else {
this.logger.info('Media sync: running incremental sync (top 10 chats)...');
await this.chatService.refreshTopChats(10);
this.lastSyncChats = 10;
}
this.lastSyncAt = Date.now();
this.lastSyncDurationMs = Date.now() - start;
this.logger.info(
`Media sync: completed in ${Math.round(this.lastSyncDurationMs / 1000)}s` +
` (${this.lastSyncChats} chats)`
);
} catch (error) {
this.lastError = error instanceof Error ? error.message : String(error);
this.lastSyncDurationMs = Date.now() - start;
this.logger.error('Media sync: failed:', error);
} finally {
this.syncing = false;
}
}
}