fix: better rebuilding of database
This commit is contained in:
7
.claude/settings.local.json
Normal file
7
.claude/settings.local.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(npm run build:*)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -545,6 +545,9 @@ export class MediaEngine extends EventEmitter {
|
|||||||
|
|
||||||
onProgress(0, 'Deleting existing media for project...');
|
onProgress(0, 'Deleting existing media for project...');
|
||||||
|
|
||||||
|
// Notify UI that rebuild is starting so it can clear the list
|
||||||
|
this.emit('rebuildStarted');
|
||||||
|
|
||||||
// Delete all media for the current project - clean slate rebuild
|
// Delete all media for the current project - clean slate rebuild
|
||||||
const existingMedia = await db.select({ id: media.id }).from(media).where(eq(media.projectId, this.currentProjectId)).all();
|
const existingMedia = await db.select({ id: media.id }).from(media).where(eq(media.projectId, this.currentProjectId)).all();
|
||||||
if (existingMedia.length > 0) {
|
if (existingMedia.length > 0) {
|
||||||
@@ -586,7 +589,7 @@ export class MediaEngine extends EventEmitter {
|
|||||||
const mediaFilePath = sidecarPath.replace('.meta', '');
|
const mediaFilePath = sidecarPath.replace('.meta', '');
|
||||||
const metaFileName = path.basename(sidecarPath);
|
const metaFileName = path.basename(sidecarPath);
|
||||||
|
|
||||||
onProgress(10 + (80 * (i / metaFiles.length)), `Processing ${metaFileName}...`);
|
onProgress(10 + (80 * (i / metaFiles.length)), `Processing ${i + 1}/${metaFiles.length}: ${metaFileName}`);
|
||||||
|
|
||||||
const metadata = await this.readSidecarFile(sidecarPath);
|
const metadata = await this.readSidecarFile(sidecarPath);
|
||||||
|
|
||||||
@@ -621,6 +624,11 @@ export class MediaEngine extends EventEmitter {
|
|||||||
console.error(`Media file not found for sidecar: ${sidecarPath}`, error);
|
console.error(`Media file not found for sidecar: ${sidecarPath}`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Yield to event loop periodically so the window stays responsive
|
||||||
|
if (i % 10 === 0) {
|
||||||
|
await new Promise(resolve => setImmediate(resolve));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onProgress(100, 'Database rebuild complete');
|
onProgress(100, 'Database rebuild complete');
|
||||||
|
|||||||
@@ -897,6 +897,9 @@ export class PostEngine extends EventEmitter {
|
|||||||
|
|
||||||
onProgress(0, 'Deleting existing posts for project...');
|
onProgress(0, 'Deleting existing posts for project...');
|
||||||
|
|
||||||
|
// Notify UI that rebuild is starting so it can clear the list
|
||||||
|
this.emit('rebuildStarted');
|
||||||
|
|
||||||
// Delete all posts for the current project - clean slate rebuild
|
// Delete all posts for the current project - clean slate rebuild
|
||||||
const existingPosts = await db.select({ id: posts.id }).from(posts).where(eq(posts.projectId, this.currentProjectId)).all();
|
const existingPosts = await db.select({ id: posts.id }).from(posts).where(eq(posts.projectId, this.currentProjectId)).all();
|
||||||
if (existingPosts.length > 0) {
|
if (existingPosts.length > 0) {
|
||||||
@@ -951,7 +954,7 @@ export class PostEngine extends EventEmitter {
|
|||||||
const filePath = mdFiles[i];
|
const filePath = mdFiles[i];
|
||||||
const fileName = path.basename(filePath);
|
const fileName = path.basename(filePath);
|
||||||
|
|
||||||
onProgress(10 + (80 * (i / mdFiles.length)), `Processing ${fileName}...`);
|
onProgress(10 + (80 * (i / mdFiles.length)), `Processing ${i + 1}/${mdFiles.length}: ${fileName}`);
|
||||||
|
|
||||||
const postData = await this.readPostFile(filePath);
|
const postData = await this.readPostFile(filePath);
|
||||||
|
|
||||||
@@ -1007,6 +1010,11 @@ export class PostEngine extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Yield to event loop periodically so the window stays responsive
|
||||||
|
if (i % 10 === 0) {
|
||||||
|
await new Promise(resolve => setImmediate(resolve));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onProgress(100, 'Database rebuild complete');
|
onProgress(100, 'Database rebuild complete');
|
||||||
|
|||||||
@@ -83,13 +83,20 @@ export class TaskManager extends EventEmitter {
|
|||||||
this.emit('taskStarted', progress);
|
this.emit('taskStarted', progress);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let lastEmitTime = 0;
|
||||||
|
const THROTTLE_MS = 250; // Only emit progress to renderer every 250ms
|
||||||
|
|
||||||
const result = await task.execute((progressValue, message) => {
|
const result = await task.execute((progressValue, message) => {
|
||||||
if (abortController.signal.aborted) {
|
if (abortController.signal.aborted) {
|
||||||
throw new Error('Task cancelled');
|
throw new Error('Task cancelled');
|
||||||
}
|
}
|
||||||
progress.progress = progressValue;
|
progress.progress = progressValue;
|
||||||
progress.message = message;
|
progress.message = message;
|
||||||
this.emit('taskProgress', progress);
|
const now = Date.now();
|
||||||
|
if (now - lastEmitTime >= THROTTLE_MS || progressValue >= 100) {
|
||||||
|
lastEmitTime = now;
|
||||||
|
this.emit('taskProgress', { ...progress });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
progress.status = 'completed';
|
progress.status = 'completed';
|
||||||
|
|||||||
@@ -137,7 +137,10 @@ export function registerIpcHandlers(): void {
|
|||||||
if (project) {
|
if (project) {
|
||||||
engine.setProjectContext(project.id);
|
engine.setProjectContext(project.id);
|
||||||
}
|
}
|
||||||
return engine.rebuildDatabaseFromFiles();
|
// Fire and forget - don't await, let it run in background
|
||||||
|
engine.rebuildDatabaseFromFiles().catch(err => {
|
||||||
|
console.error('Post rebuild failed:', err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('posts:search', async (_, query: string) => {
|
ipcMain.handle('posts:search', async (_, query: string) => {
|
||||||
@@ -256,7 +259,10 @@ export function registerIpcHandlers(): void {
|
|||||||
if (project) {
|
if (project) {
|
||||||
engine.setProjectContext(project.id);
|
engine.setProjectContext(project.id);
|
||||||
}
|
}
|
||||||
return engine.rebuildDatabaseFromFiles();
|
// Fire and forget - don't await, let it run in background
|
||||||
|
engine.rebuildDatabaseFromFiles().catch(err => {
|
||||||
|
console.error('Media rebuild failed:', err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('media:getThumbnail', async (_, id: string, size?: 'small' | 'medium' | 'large') => {
|
ipcMain.handle('media:getThumbnail', async (_, id: string, size?: 'small' | 'medium' | 'large') => {
|
||||||
@@ -440,11 +446,13 @@ export function registerIpcHandlers(): void {
|
|||||||
postEngine.on('postCreated', forwardEvent('post:created'));
|
postEngine.on('postCreated', forwardEvent('post:created'));
|
||||||
postEngine.on('postUpdated', forwardEvent('post:updated'));
|
postEngine.on('postUpdated', forwardEvent('post:updated'));
|
||||||
postEngine.on('postDeleted', forwardEvent('post:deleted'));
|
postEngine.on('postDeleted', forwardEvent('post:deleted'));
|
||||||
|
postEngine.on('rebuildStarted', forwardEvent('posts:rebuildStarted'));
|
||||||
postEngine.on('databaseRebuilt', forwardEvent('posts:databaseRebuilt'));
|
postEngine.on('databaseRebuilt', forwardEvent('posts:databaseRebuilt'));
|
||||||
|
|
||||||
mediaEngine.on('mediaImported', forwardEvent('media:imported'));
|
mediaEngine.on('mediaImported', forwardEvent('media:imported'));
|
||||||
mediaEngine.on('mediaUpdated', forwardEvent('media:updated'));
|
mediaEngine.on('mediaUpdated', forwardEvent('media:updated'));
|
||||||
mediaEngine.on('mediaDeleted', forwardEvent('media:deleted'));
|
mediaEngine.on('mediaDeleted', forwardEvent('media:deleted'));
|
||||||
|
mediaEngine.on('rebuildStarted', forwardEvent('media:rebuildStarted'));
|
||||||
mediaEngine.on('databaseRebuilt', forwardEvent('media:databaseRebuilt'));
|
mediaEngine.on('databaseRebuilt', forwardEvent('media:databaseRebuilt'));
|
||||||
|
|
||||||
syncEngine.on('syncStarted', forwardEvent('sync:started'));
|
syncEngine.on('syncStarted', forwardEvent('sync:started'));
|
||||||
|
|||||||
@@ -144,6 +144,13 @@ const App: React.FC = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Task events
|
// Task events
|
||||||
|
unsubscribers.push(
|
||||||
|
window.electronAPI?.on('task:started', (task: unknown) => {
|
||||||
|
const t = task as TaskProgress;
|
||||||
|
updateTask(t.taskId, t);
|
||||||
|
}) || (() => {})
|
||||||
|
);
|
||||||
|
|
||||||
unsubscribers.push(
|
unsubscribers.push(
|
||||||
window.electronAPI?.on('task:progress', (task: unknown) => {
|
window.electronAPI?.on('task:progress', (task: unknown) => {
|
||||||
const t = task as TaskProgress;
|
const t = task as TaskProgress;
|
||||||
@@ -235,22 +242,47 @@ const App: React.FC = () => {
|
|||||||
}) || (() => {})
|
}) || (() => {})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Rebuild events - clear store on start, reload on complete
|
||||||
unsubscribers.push(
|
unsubscribers.push(
|
||||||
window.electronAPI?.on('menu:rebuildDatabase', async () => {
|
window.electronAPI?.on('posts:rebuildStarted', () => {
|
||||||
await window.electronAPI?.posts.rebuildFromFiles();
|
setPosts([], false, 0);
|
||||||
await window.electronAPI?.media.rebuildFromFiles();
|
setSelectedPost(null);
|
||||||
// Reload data
|
}) || (() => {})
|
||||||
const posts = await window.electronAPI?.posts.getAll();
|
);
|
||||||
if (posts) {
|
|
||||||
setPosts(posts as PostData[]);
|
unsubscribers.push(
|
||||||
|
window.electronAPI?.on('posts:databaseRebuilt', async () => {
|
||||||
|
const postsResult = await window.electronAPI?.posts.getAll({ limit: 500, offset: 0 });
|
||||||
|
if (postsResult) {
|
||||||
|
const { items, hasMore, total } = postsResult as { items: PostData[]; hasMore: boolean; total: number };
|
||||||
|
setPosts(items, hasMore, total);
|
||||||
}
|
}
|
||||||
const media = await window.electronAPI?.media.getAll();
|
}) || (() => {})
|
||||||
if (media) {
|
);
|
||||||
setMedia(media as MediaData[]);
|
|
||||||
|
unsubscribers.push(
|
||||||
|
window.electronAPI?.on('media:rebuildStarted', () => {
|
||||||
|
setMedia([]);
|
||||||
|
}) || (() => {})
|
||||||
|
);
|
||||||
|
|
||||||
|
unsubscribers.push(
|
||||||
|
window.electronAPI?.on('media:databaseRebuilt', async () => {
|
||||||
|
const mediaResult = await window.electronAPI?.media.getAll();
|
||||||
|
if (mediaResult) {
|
||||||
|
setMedia(mediaResult as MediaData[]);
|
||||||
}
|
}
|
||||||
}) || (() => {})
|
}) || (() => {})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
unsubscribers.push(
|
||||||
|
window.electronAPI?.on('menu:rebuildDatabase', () => {
|
||||||
|
// Fire and forget - the handlers return immediately now
|
||||||
|
window.electronAPI?.posts.rebuildFromFiles();
|
||||||
|
window.electronAPI?.media.rebuildFromFiles();
|
||||||
|
}) || (() => {})
|
||||||
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribers.forEach(unsub => unsub());
|
unsubscribers.forEach(unsub => unsub());
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -255,9 +255,14 @@ export const useAppStore = create<AppState>()(
|
|||||||
|
|
||||||
// Task Actions
|
// Task Actions
|
||||||
setTasks: (tasks) => set({ tasks }),
|
setTasks: (tasks) => set({ tasks }),
|
||||||
updateTask: (taskId, task) => set((state) => ({
|
updateTask: (taskId, task) => set((state) => {
|
||||||
tasks: state.tasks.map((t) => (t.taskId === taskId ? { ...t, ...task } : t)),
|
const exists = state.tasks.some((t) => t.taskId === taskId);
|
||||||
})),
|
if (exists) {
|
||||||
|
return { tasks: state.tasks.map((t) => (t.taskId === taskId ? { ...t, ...task } : t)) };
|
||||||
|
}
|
||||||
|
// Add new task if it doesn't exist yet
|
||||||
|
return { tasks: [...state.tasks, { taskId, status: 'running', progress: 0, message: '', startTime: new Date().toISOString(), ...task } as TaskProgress] };
|
||||||
|
}),
|
||||||
|
|
||||||
// Sync Actions
|
// Sync Actions
|
||||||
setSyncStatus: (syncStatus) => set({ syncStatus }),
|
setSyncStatus: (syncStatus) => set({ syncStatus }),
|
||||||
|
|||||||
Reference in New Issue
Block a user