refactor: encapsulate cache rename

Change-Id: I89ee56c071c8e5404f8eb9ea744172957811d714
This commit is contained in:
wujunchen 2026-04-26 18:05:38 +08:00
parent fe05ed1a2d
commit ebc882c2f1
5 changed files with 70 additions and 19 deletions

28
main.js

File diff suppressed because one or more lines are too long

View file

@ -386,11 +386,7 @@ class ParallelReaderPlugin extends Plugin {
return;
}
if (!wasMarkdown) return;
if (this.cacheManager.cache[oldPath]) {
this.cacheManager.cache[file.path] = this.cacheManager.cache[oldPath];
delete this.cacheManager.cache[oldPath];
await this.cacheManager.save();
}
await this.cacheManager.move(oldPath, file.path);
const view = this.getParallelView();
if (view?.sourceFile && (view.sourceFile.path === oldPath || view.sourceFile.path === file.path)) {
view.sourceFile = file;
@ -726,6 +722,7 @@ export const __test = {
batchProgressVars,
buildPrompts,
cardsToMarkdown,
CacheManager,
cacheEntryMatches,
cancellationNoticeKey,
classifyGenerationError,

View file

@ -114,6 +114,24 @@ export class CacheManager {
return entry;
}
async move(oldPath: string, newPath: string): Promise<boolean> {
if (typeof oldPath !== 'string' || typeof newPath !== 'string') return false;
if (!oldPath.trim() || !newPath.trim()) return false;
if (oldPath === newPath) return Object.hasOwn(this.cache, oldPath);
const entry = this.cache[oldPath];
if (!entry) return false;
if (Object.hasOwn(this.cache, newPath)) return false;
const nextCache = {
...this.cache,
[newPath]: entry,
};
delete nextCache[oldPath];
this.cache = nextCache;
await this.save();
return true;
}
async put(filePath: string, content: string, cards: RawCard[], settings: PluginSettings): Promise<void> {
const now = new Date().toISOString();
this.cache[filePath] = {

View file

@ -55,6 +55,8 @@ assert.ok(/flushCacheSave\s*\(/.test(mainSource), 'pending cache touches should
assert.ok(/onunload[\s\S]*flushCacheSave/.test(mainSource), 'plugin unload should flush pending cache touches');
assert.ok(/cacheTouch[\s\S]*scheduleCacheSave/.test(mainSource), 'cacheTouch should schedule a cache save');
assert.ok(!/cacheTouch[\s\S]{0,220}await this\.saveCache/.test(mainSource), 'cacheTouch should not synchronously write cache.json');
assert.ok(/handleFileRename[\s\S]*cacheManager\.move/.test(mainSource), 'file rename should delegate cache moves');
assert.ok(!/handleFileRename[\s\S]*cacheManager\.cache\[/.test(mainSource), 'file rename should not mutate cache directly');
assert.ok(!/function addIconButton/.test(mainSource), 'UI icon helper should live outside main.ts');
assert.ok(!/function addTextButton/.test(mainSource), 'UI text-button helper should live outside main.ts');
assert.ok(!/function copyToClipboard/.test(mainSource), 'clipboard helper should live outside main.ts');
@ -72,6 +74,7 @@ assert.strictEqual(typeof t.findLineForAnchor, 'function');
assert.strictEqual(typeof t.folderPathsForTarget, 'function');
assert.strictEqual(typeof t.getApiBaseUrl, 'function');
assert.strictEqual(typeof t.generationFingerprint, 'function');
assert.strictEqual(typeof t.CacheManager, 'function');
assert.strictEqual(typeof t.GenerationJobManager, 'function');
assert.strictEqual(typeof t.modelForApi, 'function');
assert.strictEqual(typeof t.activeSectionLine, 'function');

View file

@ -112,6 +112,38 @@ assert.strictEqual(parsed.version, 1, 'cache file has version 1');
assert.ok(parsed.entries['a.md'], 'cache file has entries');
assert.strictEqual(serialized.includes('\n'), false, 'cache file is single line');
async function testCacheManagerMove() {
const writes = [];
const adapter = {
exists: async () => true,
mkdir: async () => {},
read: async () => '{}',
write: async (filePath, content) => writes.push({ filePath, content }),
};
const manager = new t.CacheManager(adapter, '.obsidian', 'parallel-reader', () => ({
maxCacheEntries: 100,
}));
const cacheEntry = { generatedAt: '2024-01-01T00:00:00.000Z', cards: [{ title: 'Card' }] };
manager.cache = {
'old.md': cacheEntry,
'other.md': { generatedAt: '2024-01-02T00:00:00.000Z', cards: [] },
};
assert.strictEqual(await manager.move('missing.md', 'new.md'), false, 'missing cache move returns false');
assert.strictEqual(await manager.move('missing.md', 'missing.md'), false, 'missing same-path move returns false');
assert.strictEqual(await manager.move(' ', 'new.md'), false, 'blank source path is rejected');
assert.strictEqual(await manager.move('old.md', ' '), false, 'blank target path is rejected');
assert.strictEqual(await manager.move('old.md', 'old.md'), true, 'same-path move is a no-op success');
assert.strictEqual(await manager.move('old.md', 'other.md'), false, 'move does not overwrite an existing target path');
assert.strictEqual(writes.length, 0, 'no-op and rejected cache moves are not persisted');
assert.strictEqual(await manager.move('old.md', 'new.md'), true, 'existing cache move returns true');
assert.strictEqual(manager.cache['old.md'], undefined, 'old cache path is removed');
assert.deepStrictEqual(manager.cache['new.md'], cacheEntry, 'cache entry is moved to new path');
assert.ok(manager.cache['other.md'], 'unrelated cache entries remain');
assert.strictEqual(writes.length, 1, 'successful cache move is persisted once');
assert.ok(JSON.parse(writes[0].content).entries['new.md'], 'persisted cache uses moved path');
}
assert.strictEqual(t.shouldConfirmRegenerate(null, true), false, 'null entry never confirms');
assert.strictEqual(t.shouldConfirmRegenerate(null, false), false);
assert.strictEqual(t.shouldConfirmRegenerate({ generatedAt: '2024-01-01' }, true), false, 'no updatedAt => no confirm');
@ -606,6 +638,7 @@ try {
// Wrap the tail in an async runner so module-level tests can include async cases.
(async () => {
await testCacheManagerMove();
await testSummarizeDocumentAnchorSorting();
console.log('modules tests passed');
})().catch((e) => {