Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions folder-index-state.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const fs = require('fs');
const path = require('path');

function getFolderIndexMtimeMs(folderPath) {
let indexMtimeMs = 0;

try {
indexMtimeMs = fs.statSync(folderPath).mtimeMs;
} catch {
return 0;
}

try {
// Session files are appended in place, which updates the file mtime but
// often leaves the containing directory mtime unchanged.
const entries = fs.readdirSync(folderPath, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isFile() || !entry.name.endsWith('.jsonl')) continue;
try {
const fileMtimeMs = fs.statSync(path.join(folderPath, entry.name)).mtimeMs;
if (fileMtimeMs > indexMtimeMs) indexMtimeMs = fileMtimeMs;
} catch {}
}
} catch {}

return indexMtimeMs;
}

module.exports = { getFolderIndexMtimeMs };
17 changes: 6 additions & 11 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const fs = require('fs');
const os = require('os');
const pty = require('node-pty');
const log = require('electron-log');
const { getFolderIndexMtimeMs } = require('./folder-index-state');
log.transports.file.level = app.isPackaged ? 'info' : 'debug';
log.transports.console.level = app.isPackaged ? 'info' : 'debug';

Expand Down Expand Up @@ -347,10 +348,7 @@ function refreshFolder(folder) {

const projectPath = deriveProjectPath(folderPath, folder);
if (!projectPath) {
// Still record mtime so backgroundRefresh doesn't keep retrying
let mtimeMs = 0;
try { mtimeMs = fs.statSync(folderPath).mtimeMs; } catch {}
setFolderMeta(folder, null, mtimeMs);
setFolderMeta(folder, null, getFolderIndexMtimeMs(folderPath));
return;
}

Expand Down Expand Up @@ -407,9 +405,7 @@ function refreshFolder(folder) {
}

// Update folder mtime
let mtimeMs = 0;
try { mtimeMs = fs.statSync(folderPath).mtimeMs; } catch {}
setFolderMeta(folder, projectPath, mtimeMs);
setFolderMeta(folder, projectPath, getFolderIndexMtimeMs(folderPath));
}

/** Populate entire cache from filesystem (cold start) */
Expand Down Expand Up @@ -505,8 +501,7 @@ function backgroundRefresh() {
// Check for new/changed folders
for (const folder of folders) {
const folderPath = path.join(PROJECTS_DIR, folder);
let currentMtime = 0;
try { currentMtime = fs.statSync(folderPath).mtimeMs; } catch {}
const currentMtime = getFolderIndexMtimeMs(folderPath);

const cached = metaMap.get(folder);
if (!cached || cached.indexMtimeMs !== currentMtime) {
Expand Down Expand Up @@ -576,7 +571,7 @@ function populateCacheViaWorker() {

// Write results to DB on main thread (fast)
let sessionCount = 0;
for (const { folder, projectPath, sessions, mtimeMs } of msg.results) {
for (const { folder, projectPath, sessions, indexMtimeMs } of msg.results) {
deleteCachedFolder(folder);
deleteSearchFolder(folder);
if (sessions.length > 0) {
Expand All @@ -590,7 +585,7 @@ function populateCacheViaWorker() {
if (s.customTitle) setName(s.sessionId, s.customTitle);
}
}
setFolderMeta(folder, projectPath, mtimeMs);
setFolderMeta(folder, projectPath, indexMtimeMs);
}

populatingCache = false;
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"main": "main.js",
"scripts": {
"start": "npm run bundle:codemirror && electron .",
"test": "node --test",
"electron": "electron .",
"bundle:codemirror": "esbuild public/codemirror-setup.js --bundle --outfile=public/codemirror-bundle.js --format=iife --platform=browser --minify",
"generate-icons": "node scripts/generate-icons.js && node scripts/generate-dmg-background.js",
Expand Down
28 changes: 28 additions & 0 deletions test/folder-index-state.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const os = require('os');
const path = require('path');

const { getFolderIndexMtimeMs } = require('../folder-index-state');

test('folder index timestamp advances when an existing session file is appended', async () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'switchboard-folder-index-'));

try {
const sessionPath = path.join(tmpDir, 'session.jsonl');
fs.writeFileSync(sessionPath, '{"type":"user","message":"first"}\n', 'utf8');

const before = getFolderIndexMtimeMs(tmpDir);

await new Promise(resolve => setTimeout(resolve, 1100));

fs.appendFileSync(sessionPath, '{"type":"assistant","message":"second"}\n', 'utf8');

const after = getFolderIndexMtimeMs(tmpDir);

assert.ok(after > before, `expected index mtime to increase (${before} -> ${after})`);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});
6 changes: 3 additions & 3 deletions workers/scan-projects.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const { parentPort, workerData } = require('worker_threads');
const fs = require('fs');
const path = require('path');
const { getFolderIndexMtimeMs } = require('../folder-index-state');

const PROJECTS_DIR = workerData.projectsDir;

Expand Down Expand Up @@ -49,8 +50,7 @@ function readFolderFromFilesystem(folder) {
const projectPath = deriveProjectPath(folderPath, folder);
if (!projectPath) return null;
const sessions = [];
let mtimeMs = 0;
try { mtimeMs = fs.statSync(folderPath).mtimeMs; } catch {}
const indexMtimeMs = getFolderIndexMtimeMs(folderPath);

try {
const jsonlFiles = fs.readdirSync(folderPath).filter(f => f.endsWith('.jsonl'));
Expand Down Expand Up @@ -99,7 +99,7 @@ function readFolderFromFilesystem(folder) {
}
} catch {}

return { folder, projectPath, sessions, mtimeMs };
return { folder, projectPath, sessions, indexMtimeMs };
}

// Scan all folders
Expand Down
Loading