mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 16:30:27 +00:00
Add Exception helper class for consistent error handling and logging. Replace throw statements and console.error calls with Exception methods. Update service methods to return Error | T instead of mixed success/failure objects. Improve type safety in Claude.extractContents with explicit return type. Add WikiLinks helper to VaultCacheService for managing wiki link references. Update unit tests.
200 lines
No EOL
6.6 KiB
TypeScript
200 lines
No EOL
6.6 KiB
TypeScript
import type VaultkeeperAIPlugin from "main";
|
|
import { Resolve } from "./DependencyService";
|
|
import { Services } from "./Services";
|
|
import type { VaultService } from "./VaultService";
|
|
import { FileEvent } from "Enums/FileEvent";
|
|
import { getAllTags, MetadataCache, TFile, TFolder } from "obsidian";
|
|
import { FileTagMapping } from "Helpers/FileTagMapping";
|
|
import * as fuzzysort from "fuzzysort";
|
|
import { Path } from "Enums/Path";
|
|
import { WikiLinks } from "Helpers/WikiLinks";
|
|
|
|
// Note that 'files' actually refers to both directories and files (Obsidian naming)
|
|
|
|
export class VaultCacheService {
|
|
|
|
public wikiLinks: WikiLinks = new WikiLinks();
|
|
|
|
private readonly fuzzysortOptions = {
|
|
limit: 10,
|
|
all: false,
|
|
key: "prepared"
|
|
};
|
|
|
|
private readonly plugin: VaultkeeperAIPlugin;
|
|
private readonly vaultService: VaultService;
|
|
private readonly metaDataCache: MetadataCache;
|
|
|
|
private tags: Set<string> = new Set();
|
|
private files: Map<string, TFile> = new Map();
|
|
private folders: Map<string, TFolder> = new Map();
|
|
private mapping: FileTagMapping = new FileTagMapping();
|
|
|
|
private preparedTags: { prepared: Fuzzysort.Prepared, tag: string }[] = [];
|
|
private preparedFiles: { prepared: Fuzzysort.Prepared, file: TFile }[] = [];
|
|
private preparedFolders: { prepared: Fuzzysort.Prepared, folder: TFolder }[] = [];
|
|
|
|
private initialised = false;
|
|
|
|
public constructor() {
|
|
this.plugin = Resolve<VaultkeeperAIPlugin>(Services.VaultkeeperAIPlugin);
|
|
this.vaultService = Resolve<VaultService>(Services.VaultService);
|
|
this.metaDataCache = this.plugin.app.metadataCache;
|
|
this.registerFileEvents();
|
|
|
|
this.plugin.app.metadataCache.on("resolved", async () => {
|
|
if (!this.initialised) {
|
|
await this.setupCaches();
|
|
this.initialised = true;
|
|
}
|
|
});
|
|
}
|
|
|
|
public matchTag(input: string): Fuzzysort.KeyResults<{ prepared: Fuzzysort.Prepared, tag: string }> {
|
|
return fuzzysort.go(input.toLowerCase(), this.preparedTags, this.fuzzysortOptions);
|
|
}
|
|
|
|
public matchFile(input: string): Fuzzysort.KeyResults<{ prepared: Fuzzysort.Prepared, file: TFile }> {
|
|
return fuzzysort.go(input.toLowerCase(), this.preparedFiles, this.fuzzysortOptions);
|
|
}
|
|
|
|
public matchFolder(input: string): Fuzzysort.KeyResults<{ prepared: Fuzzysort.Prepared, folder: TFolder }> {
|
|
return fuzzysort.go(input.toLowerCase(), this.preparedFolders, this.fuzzysortOptions);
|
|
}
|
|
|
|
private registerFileEvents() {
|
|
this.vaultService.registerFileEvents((event, file, args) => {
|
|
const shouldCacheNewPath = this.shouldBeCached(file.path);
|
|
const shouldCacheOldPath = args.oldPath ? this.shouldBeCached(args.oldPath) : false;
|
|
|
|
if (!shouldCacheNewPath && !shouldCacheOldPath) {
|
|
return;
|
|
}
|
|
|
|
if (file instanceof TFile) {
|
|
switch (event) {
|
|
case FileEvent.Create:
|
|
if (shouldCacheNewPath) {
|
|
this.wikiLinks.addWikiLink(file);
|
|
this.files.set(file.path, file);
|
|
this.cacheTags(file);
|
|
}
|
|
break;
|
|
|
|
case FileEvent.Modify:
|
|
if (shouldCacheNewPath) {
|
|
const newTags = this.getTags(file);
|
|
const removedTags = this.mapping.updateMapping(file.path, newTags);
|
|
removedTags.forEach(tag => this.tags.delete(tag));
|
|
this.cacheTags(file, newTags);
|
|
}
|
|
break;
|
|
|
|
case FileEvent.Rename:
|
|
if (shouldCacheOldPath) {
|
|
this.wikiLinks.removeWikiLink(args.oldPath);
|
|
this.files.delete(args.oldPath);
|
|
const orphanedTags = this.mapping.deleteFromMapping(args.oldPath);
|
|
orphanedTags.forEach(tag => this.tags.delete(tag));
|
|
}
|
|
if (shouldCacheNewPath) {
|
|
this.wikiLinks.addWikiLink(file);
|
|
this.mapping.renameKey(args.oldPath, file.path);
|
|
this.files.set(file.path, file);
|
|
this.cacheTags(file);
|
|
}
|
|
break;
|
|
|
|
case FileEvent.Delete:
|
|
if (shouldCacheOldPath) {
|
|
this.wikiLinks.removeWikiLink(file);
|
|
this.files.delete(args.oldPath);
|
|
const orphanedTags = this.mapping.deleteFromMapping(args.oldPath);
|
|
orphanedTags.forEach(tag => this.tags.delete(tag));
|
|
}
|
|
break;
|
|
}
|
|
this.fuzzySortPrepareTags();
|
|
this.fuzzySortPrepareFiles();
|
|
} else if (file instanceof TFolder) {
|
|
switch (event) {
|
|
case FileEvent.Create:
|
|
if (shouldCacheNewPath) {
|
|
this.folders.set(file.path, file);
|
|
}
|
|
break;
|
|
|
|
case FileEvent.Rename:
|
|
if (shouldCacheOldPath) {
|
|
this.folders.delete(args.oldPath);
|
|
}
|
|
if (shouldCacheNewPath) {
|
|
this.folders.set(file.path, file);
|
|
}
|
|
break;
|
|
|
|
case FileEvent.Delete:
|
|
if (shouldCacheOldPath) {
|
|
this.folders.delete(args.oldPath);
|
|
}
|
|
break;
|
|
|
|
case FileEvent.Modify:
|
|
break; // ignore modifications for folders
|
|
}
|
|
this.fuzzySortPrepareFolders();
|
|
}
|
|
});
|
|
}
|
|
|
|
private async setupCaches() {
|
|
(await this.vaultService.listDirectoryContents(Path.Root)).forEach(file => {
|
|
if (file instanceof TFile) {
|
|
this.wikiLinks.addWikiLink(file);
|
|
this.files.set(file.path, file);
|
|
this.cacheTags(file);
|
|
} else if (file instanceof TFolder) {
|
|
this.folders.set(file.path, file);
|
|
}
|
|
});
|
|
this.fuzzySortPrepareTags();
|
|
this.fuzzySortPrepareFiles();
|
|
this.fuzzySortPrepareFolders();
|
|
}
|
|
|
|
private cacheTags(file: TFile, fileTags?: string[]) {
|
|
const tags = fileTags ?? this.getTags(file);
|
|
tags.forEach(tag => this.tags.add(tag));
|
|
this.mapping.set(file.path, tags);
|
|
}
|
|
|
|
private getTags(file: TFile): string[] {
|
|
const metaData = this.metaDataCache.getCache(file.path);
|
|
return metaData ? (getAllTags(metaData) ?? []) : [];
|
|
}
|
|
|
|
private fuzzySortPrepareTags() {
|
|
this.preparedTags = [];
|
|
this.tags.forEach(tag => {
|
|
this.preparedTags.push({ prepared: fuzzysort.prepare(tag), tag: tag });
|
|
});
|
|
}
|
|
|
|
private fuzzySortPrepareFiles() {
|
|
this.preparedFiles = [];
|
|
this.files.forEach(file => {
|
|
this.preparedFiles.push({ prepared: fuzzysort.prepare(file.basename), file: file });
|
|
});
|
|
}
|
|
|
|
private fuzzySortPrepareFolders() {
|
|
this.preparedFolders = [];
|
|
this.folders.forEach(folder => {
|
|
this.preparedFolders.push({ prepared: fuzzysort.prepare(folder.path), folder: folder });
|
|
});
|
|
}
|
|
|
|
private shouldBeCached(path: string) {
|
|
return !this.vaultService.isExclusion(path, false);
|
|
}
|
|
} |