prevent conflic between obsidian sync and headless sync.

This commit is contained in:
Nystik
2026-03-30 22:15:10 +02:00
parent 300e251734
commit 0d7f36ca9b
8 changed files with 212 additions and 109 deletions

View File

@@ -7,6 +7,7 @@ import { createFsWatch } from "./watch.js";
import { createWatcherClient } from "./watcher-client.js";
import { createFdOps } from "./fd.js";
import { constants } from "./constants.js";
import { registerReadTransform, removeReadTransform } from "./read-transforms.js";
const metadataCache = new MetadataCache();
const contentCache = new ContentCache();
@@ -43,6 +44,8 @@ export const fsShim = {
_metadataCache: metadataCache,
_contentCache: contentCache,
_watcherClient: watcherClient,
_registerReadTransform: registerReadTransform,
_removeReadTransform: removeReadTransform,
async _init(basePath) {
const tree = await transport.fetchTree(basePath);

View File

@@ -1,5 +1,6 @@
import { markLocalOp } from "./echo-guard.js";
import { isInputCachePath, inputCacheGet } from "./input-cache.js";
import { applyReadTransform } from "./read-transforms.js";
export function createFsPromises(metadataCache, contentCache, transport) {
return {
@@ -46,60 +47,52 @@ export function createFsPromises(metadataCache, contentCache, transport) {
const wantText = encoding === "utf8" || encoding === "utf-8";
let result = null;
// Check input cache for files picked via browser file dialogs.
if (isInputCachePath(path)) {
const inputData = inputCacheGet(path);
if (inputData !== null) {
if (wantText) {
return typeof inputData === "string"
? inputData
: new TextDecoder().decode(inputData);
}
if (typeof inputData === "string") {
return new TextEncoder().encode(inputData);
}
return inputData;
}
result = inputCacheGet(path);
}
const meta = metadataCache.get(path);
if (meta && meta.type === "directory") {
const e = new Error("EISDIR: illegal operation on a directory, read");
e.code = "EISDIR";
throw e;
}
if (result === null) {
const meta = metadataCache.get(path);
if (!meta && path) {
const e = new Error(
`ENOENT: no such file or directory, open '${path}'`,
);
e.code = "ENOENT";
throw e;
}
const cached = contentCache.get(path);
if (cached !== null) {
if (wantText) {
return typeof cached === "string"
? cached
: new TextDecoder().decode(cached);
if (meta && meta.type === "directory") {
const e = new Error("EISDIR: illegal operation on a directory, read");
e.code = "EISDIR";
throw e;
}
// binary. ensure we return a proper Uint8Array with .buffer
if (typeof cached === "string") {
return new TextEncoder().encode(cached);
if (!meta && path) {
const e = new Error(
`ENOENT: no such file or directory, open '${path}'`,
);
e.code = "ENOENT";
throw e;
}
return cached;
result = contentCache.get(path);
}
const data = await transport.readFile(path, encoding);
contentCache.set(path, data);
return data;
if (result === null) {
result = await transport.readFile(path, encoding);
contentCache.set(path, result);
}
// Apply registered read transforms (e.g., patching synced config files).
result = applyReadTransform(path, result);
if (wantText) {
return typeof result === "string"
? result
: new TextDecoder().decode(result);
}
if (typeof result === "string") {
return new TextEncoder().encode(result);
}
return result;
},
async writeFile(path, data, encoding) {

View File

@@ -0,0 +1,49 @@
// Post-read transforms for specific file paths.
// Allows patching file content after reading but before returning to the caller.
// Used to prevent synced config files from activating conflicting features.
const transforms = new Map();
function normalize(p) {
return (p || "")
.replace(/\\/g, "/")
.replace(/^\/+/, "")
.replace(/\/+$/, "");
}
export function registerReadTransform(path, fn) {
transforms.set(normalize(path), fn);
}
export function removeReadTransform(path) {
transforms.delete(normalize(path));
}
export function applyReadTransform(path, data) {
const norm = normalize(path);
const fn = transforms.get(norm);
if (!fn) {
return data;
}
try {
const result = fn(data);
if (result !== data) {
const before = typeof data === "string" ? data : new TextDecoder().decode(data);
const after = typeof result === "string" ? result : new TextDecoder().decode(result);
console.log(`[shim:fs] Read transform applied: ${norm}`);
console.log(`[shim:fs] before:`, before);
console.log(`[shim:fs] after:`, after);
}
return result;
} catch {
return data;
}
}
export function hasReadTransform(path) {
return transforms.has(normalize(path));
}

View File

@@ -1,5 +1,6 @@
import { markLocalOp } from "./echo-guard.js";
import { isInputCachePath, inputCacheGet } from "./input-cache.js";
import { applyReadTransform } from "./read-transforms.js";
export function createFsSync(metadataCache, contentCache, transport) {
return {
@@ -58,6 +59,8 @@ export function createFsSync(metadataCache, contentCache, transport) {
encoding = encoding?.encoding;
}
const wantText = encoding === "utf8" || encoding === "utf-8";
const meta = metadataCache.get(path);
if (meta && meta.type === "directory") {
const e = new Error("EISDIR: illegal operation on a directory, read");
@@ -65,39 +68,37 @@ export function createFsSync(metadataCache, contentCache, transport) {
throw e;
}
let result = null;
// Check input cache for files picked via browser file dialogs.
// These never hit the server; they exist only in browser memory.
if (isInputCachePath(path)) {
const inputData = inputCacheGet(path);
if (inputData !== null) {
if (encoding === "utf8" || encoding === "utf-8") {
return typeof inputData === "string"
? inputData
: new TextDecoder().decode(inputData);
}
return inputData;
result = inputData;
}
}
const cached = contentCache.get(path);
if (cached !== null) {
if (encoding === "utf8" || encoding === "utf-8") {
return typeof cached === "string"
? cached
: new TextDecoder().decode(cached);
}
return cached;
if (result === null) {
result = contentCache.get(path);
}
console.warn("[shim:fs] readFileSync cache miss, using sync XHR:", path);
if (result === null) {
console.warn("[shim:fs] readFileSync cache miss, using sync XHR:", path);
result = transport.readFileSync(path, encoding);
contentCache.set(path, result);
}
const data = transport.readFileSync(path, encoding);
contentCache.set(path, data);
// Apply registered read transforms (e.g., patching synced config files).
result = applyReadTransform(path, result);
return data;
if (wantText) {
return typeof result === "string"
? result
: new TextDecoder().decode(result);
}
return result;
},
writeFileSync(path, data, encoding) {