mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-11-14 20:20:12 +00:00
fix(cli): skip hidden directories during vault scans
This commit is contained in:
1 parent
003173ee0e
commit
7382e155ba
5 files changed
+58
-2
No files matched your search
@@ -9,6 +9,7 @@ import type { NodeFile, NodeFolder, NodeStat } from "./NodeTypes";
|
||||
import { path } from "@vrtmrz/livesync-commonlib/node";
|
||||
import type { CliDiagnosticReporter } from "@/apps/cli/cliOutput";
|
||||
import { findCaseInsensitiveFile } from "@/apps/caseInsensitiveFileLookup";
|
||||
import { isHiddenPath } from "@/apps/cli/cliPathFilter";
|
||||
import {
|
||||
createStoragePathResolver,
|
||||
parseFilePath,
|
||||
@@ -194,6 +195,7 @@ export class NodeFileSystemAdapter implements IFileSystemAdapter<NodeFile, NodeF
|
||||
}
|
||||
|
||||
private async scanDirectoryInto(relativePath: string, files: Map<string, NodeFile>): Promise<void> {
|
||||
if (isHiddenPath(relativePath)) return;
|
||||
const resolver = this.createScanResolver();
|
||||
const filePath = parseFilePath(relativePath);
|
||||
if (resolver) {
|
||||
@@ -232,6 +234,7 @@ export class NodeFileSystemAdapter implements IFileSystemAdapter<NodeFile, NodeF
|
||||
if (entries === null) return;
|
||||
for (const entry of entries) {
|
||||
if (entry.kind === "directory") {
|
||||
if (isHiddenPath(entry.path)) continue;
|
||||
await this.scanResolvedDirectoryInto(resolver, entry.path, files);
|
||||
continue;
|
||||
}
|
||||
@@ -261,6 +264,7 @@ export class NodeFileSystemAdapter implements IFileSystemAdapter<NodeFile, NodeF
|
||||
files.set(filePath, file);
|
||||
}
|
||||
for (const entryPath of entries.folders) {
|
||||
if (isHiddenPath(entryPath)) continue;
|
||||
await this.scanLegacyDirectoryInto(entryPath, files);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -29,6 +29,53 @@ describe("NodeFileSystemAdapter file enumeration", () => {
|
||||
await Promise.all(tempDirs.splice(0).map((directory) => fs.rm(directory, { recursive: true, force: true })));
|
||||
});
|
||||
|
||||
it.each([0, 1])("does not read hidden directories in identity mode %s", async (version) => {
|
||||
const { directory } = await createVault();
|
||||
for (const hiddenFile of [".tool/state/private.txt", "folder/.private/secret.md"]) {
|
||||
await fs.mkdir(path.dirname(path.join(directory, hiddenFile)), { recursive: true });
|
||||
await fs.writeFile(path.join(directory, hiddenFile), "excluded hidden content");
|
||||
}
|
||||
const report = vi.fn();
|
||||
const adapter = new NodeFileSystemAdapter(directory, report, () => version);
|
||||
const hiddenReads: string[] = [];
|
||||
const error = Object.assign(new Error("Hidden directory is unreadable"), { code: "EACCES" });
|
||||
const checkDirectory = (relativePath: string) => {
|
||||
if (relativePath === ".tool" || relativePath === "folder/.private") {
|
||||
hiddenReads.push(relativePath);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
const readDirectory = adapter.storage.readDirectory.bind(adapter.storage);
|
||||
vi.spyOn(adapter.storage, "readDirectory").mockImplementation(async (relativePath) => {
|
||||
checkDirectory(relativePath);
|
||||
return await readDirectory(relativePath);
|
||||
});
|
||||
const list = adapter.storage.list.bind(adapter.storage);
|
||||
vi.spyOn(adapter.storage, "list").mockImplementation(async (...args) => {
|
||||
checkDirectory(args[0]);
|
||||
return await list(...args);
|
||||
});
|
||||
|
||||
expect((await adapter.getFiles()).map((file) => file.path).sort()).toEqual(paths);
|
||||
await adapter.scanDirectory();
|
||||
await adapter.scanDirectory(".tool/state");
|
||||
expect(hiddenReads).toEqual([]);
|
||||
expect(report).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([0, 1])("keeps explicit hidden file lookups available in identity mode %s", async (version) => {
|
||||
const { directory } = await createVault();
|
||||
const hiddenPath = ".tool/state/private.txt";
|
||||
await fs.mkdir(path.dirname(path.join(directory, hiddenPath)), { recursive: true });
|
||||
await fs.writeFile(path.join(directory, hiddenPath), "explicit hidden content");
|
||||
const adapter = new NodeFileSystemAdapter(directory, undefined, () => version);
|
||||
|
||||
expect(await adapter.getAbstractFileByPath(hiddenPath)).toMatchObject({ path: hiddenPath });
|
||||
expect(await adapter.storage.read(hiddenPath)).toBe(
|
||||
"explicit hidden content"
|
||||
);
|
||||
});
|
||||
|
||||
it("lists every file when one file was refreshed before the first enumeration", async () => {
|
||||
const { adapter } = await createVault();
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export function isHiddenPath(relativePath: string): boolean {
|
||||
return relativePath.split("/").some((part) => part.startsWith("."));
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import { getForceRequirementError } from "./commands/utils";
|
||||
import { getPathFromUXFileInfo } from "@vrtmrz/livesync-commonlib/compat/common/typeUtils";
|
||||
import { stripAllPrefixes } from "@vrtmrz/livesync-commonlib/compat/string_and_binary/path";
|
||||
import { IgnoreRules } from "./serviceModules/IgnoreRules";
|
||||
import { isHiddenPath } from "./cliPathFilter";
|
||||
import { useP2PReplicatorFeature, type UseP2PReplicatorResult } from "@vrtmrz/livesync-commonlib/p2p";
|
||||
import { useOfflineScanner } from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/offlineScanner";
|
||||
import type { ReplicationSchedulingControl } from "@/serviceFeatures/replicationScheduling";
|
||||
@@ -583,8 +584,7 @@ export async function main(
|
||||
core.services.vault.isTargetFile.addHandler(async (target) => {
|
||||
const targetPath = stripAllPrefixes(getPathFromUXFileInfo(target));
|
||||
const parts = targetPath.split("/");
|
||||
// if some part of the path starts with dot, treat it as internal file and ignore.
|
||||
if (parts.some((part) => part.startsWith("."))) {
|
||||
if (isHiddenPath(targetPath)) {
|
||||
return await Promise.resolve(false);
|
||||
}
|
||||
// PouchDB LevelDB database directory lives in the vault directory.
|
||||
|
||||
@@ -16,6 +16,8 @@ Earlier releases remain available in the 1.0 release history, the 1.0 preview hi
|
||||
|
||||
#### Fixed
|
||||
|
||||
- The CLI daemon now starts even when hidden directories in our Vault are unreadable. Vault scans skip these directories, which remain excluded from synchronisation. (#1325)
|
||||
|
||||
- The daemon now detects files written into newly created folders during a burst of writes, including files renamed from temporary names, without requiring a restart. (#1328)
|
||||
- The CLI requires Node.js 22.22 or later. The Docker image includes its own runtime.
|
||||
|
||||
|
||||
Reference in new issue
Block a user