mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-11-14 20:20:12 +00:00
Fix CLI filesystem case-sensitivity detection
This commit is contained in:
1 parent
525062f1b5
commit
daa67bea51
6 files changed
+143
-1
No files matched your search
@@ -22,6 +22,7 @@ import { ControlService } from "@vrtmrz/livesync-commonlib/compat/services/base/
|
||||
import { HeadlessAPIService } from "@vrtmrz/livesync-commonlib/compat/services/implements/headless/HeadlessAPIService";
|
||||
import { NodeKeyValueDBService } from "./NodeKeyValueDBService";
|
||||
import { createNodeLocalStorage } from "./NodeLocalStorage";
|
||||
import { isStorageCaseInsensitive } from "./storageCaseSensitivity";
|
||||
import { DatabaseService } from "@vrtmrz/livesync-commonlib/compat/services/base/DatabaseService";
|
||||
import type { ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import { fs, path as nodePath } from "@vrtmrz/livesync-commonlib/node";
|
||||
@@ -135,6 +136,10 @@ export class NodeServiceHub<T extends NodeServiceContext> extends InjectableServ
|
||||
settingService: setting,
|
||||
APIService: API,
|
||||
});
|
||||
let storageInsensitive: boolean | undefined;
|
||||
vault.isStorageInsensitive.setHandler(
|
||||
() => (storageInsensitive ??= isStorageCaseInsensitive(context.runtimeVaultPath))
|
||||
);
|
||||
const scopeDirectory = () => runtimeScopeDirectory(basePath, vault.getVaultName(), context.runtimeVaultPath);
|
||||
const test = new InjectableTestService(context);
|
||||
const databaseEvents = new InjectableDatabaseEventService(context);
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { fs, path } from "@vrtmrz/livesync-commonlib/node";
|
||||
|
||||
export function isStorageCaseInsensitive(vaultPath: string): boolean {
|
||||
const probePath = fs.mkdtempSync(path.join(vaultPath, ".livesync-case-"));
|
||||
try {
|
||||
const original = fs.statSync(probePath);
|
||||
const alternate = fs.statSync(path.join(vaultPath, path.basename(probePath).toUpperCase()), {
|
||||
throwIfNoEntry: false,
|
||||
});
|
||||
return alternate !== undefined && original.dev === alternate.dev && original.ino === alternate.ino;
|
||||
} finally {
|
||||
fs.rmdirSync(probePath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { fs, os, path } from "@vrtmrz/livesync-commonlib/node";
|
||||
import { isStorageCaseInsensitive } from "./storageCaseSensitivity";
|
||||
import { NodeServiceContext, NodeServiceHub } from "./NodeServiceHub";
|
||||
|
||||
vi.mock("@vrtmrz/livesync-commonlib/node", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@vrtmrz/livesync-commonlib/node")>();
|
||||
return { ...actual, fs: { ...actual.fs } };
|
||||
});
|
||||
|
||||
describe("CLI storage case sensitivity", () => {
|
||||
let directory: string;
|
||||
let vaultPath: string;
|
||||
|
||||
beforeEach(() => {
|
||||
directory = fs.mkdtempSync(path.join(os.tmpdir(), "livesync-storage-case-"));
|
||||
vaultPath = path.join(directory, "vault");
|
||||
fs.mkdirSync(vaultPath);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
fs.rmSync(directory, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("matches the Vault filesystem and leaves existing files untouched", () => {
|
||||
fs.writeFileSync(path.join(vaultPath, "Note.md"), "local content");
|
||||
const expected = fs.existsSync(path.join(vaultPath, "NOTE.MD"));
|
||||
|
||||
expect(isStorageCaseInsensitive(vaultPath)).toBe(expected);
|
||||
|
||||
expect(fs.readdirSync(vaultPath)).toEqual(["Note.md"]);
|
||||
expect(fs.readFileSync(path.join(vaultPath, "Note.md"), "utf8")).toBe("local content");
|
||||
});
|
||||
|
||||
it("recognises an alternate spelling of the same filesystem entry", () => {
|
||||
const entry = fs.statSync(vaultPath);
|
||||
vi.spyOn(fs, "statSync").mockReturnValue(entry);
|
||||
|
||||
expect(isStorageCaseInsensitive(vaultPath)).toBe(true);
|
||||
expect(fs.readdirSync(vaultPath)).toEqual([]);
|
||||
});
|
||||
|
||||
it("does not mistake a different entry for the probe", () => {
|
||||
const original = fs.statSync(vaultPath);
|
||||
const other = fs.statSync(directory);
|
||||
vi.spyOn(fs, "statSync").mockReturnValueOnce(original).mockReturnValueOnce(other);
|
||||
|
||||
expect(isStorageCaseInsensitive(vaultPath)).toBe(false);
|
||||
expect(fs.readdirSync(vaultPath)).toEqual([]);
|
||||
});
|
||||
|
||||
it("reports inspection failures and removes its probe", () => {
|
||||
const failure = Object.assign(new Error("Cannot inspect the Vault"), { code: "EACCES" });
|
||||
vi.spyOn(fs, "statSync").mockImplementation(() => {
|
||||
throw failure;
|
||||
});
|
||||
|
||||
expect(() => isStorageCaseInsensitive(vaultPath)).toThrow(failure);
|
||||
expect(fs.readdirSync(vaultPath)).toEqual([]);
|
||||
});
|
||||
|
||||
it("lazily detects the selected Vault once per service hub", () => {
|
||||
const createProbe = vi.spyOn(fs, "mkdtempSync");
|
||||
const context = new NodeServiceContext(
|
||||
directory,
|
||||
{
|
||||
readStdin: async () => "",
|
||||
prompt: async () => "",
|
||||
writeStdout: () => undefined,
|
||||
writeStderr: () => undefined,
|
||||
},
|
||||
vaultPath
|
||||
);
|
||||
const hub = new NodeServiceHub(directory, context);
|
||||
expect(createProbe).not.toHaveBeenCalled();
|
||||
|
||||
const first = hub.vault.isStorageInsensitive();
|
||||
expect(hub.vault.isStorageInsensitive()).toBe(first);
|
||||
expect(createProbe).toHaveBeenCalledExactlyOnceWith(path.join(vaultPath, ".livesync-case-"));
|
||||
expect(fs.readdirSync(vaultPath)).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user