Fix folder deletion (#140)
This commit is contained in:
parent
aa73a5d718
commit
1ddba47b80
9 changed files with 69 additions and 25 deletions
|
|
@ -14,10 +14,12 @@ export class ObsidianFileSystemOperations implements FileSystemOperations {
|
|||
private readonly workspace: Workspace
|
||||
) {}
|
||||
|
||||
public async listAllFiles(): Promise<RelativePath[]> {
|
||||
public async listFilesRecursively(
|
||||
root: RelativePath | undefined
|
||||
): Promise<RelativePath[]> {
|
||||
// Let's implement this by hand because vault.adapter.listAllFiles doesn't always return all files.
|
||||
const allFiles = [];
|
||||
const remainingFolders = [this.vault.getRoot().path];
|
||||
const remainingFolders = [root ?? this.vault.getRoot().path];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
while (true) {
|
||||
|
|
|
|||
|
|
@ -29,8 +29,10 @@ class MockDatabase implements Partial<Database> {
|
|||
class FakeFileSystemOperations implements FileSystemOperations {
|
||||
public readonly names = new Set<string>();
|
||||
|
||||
public async listAllFiles(): Promise<RelativePath[]> {
|
||||
throw new Error("Method not implemented.");
|
||||
public async listFilesRecursively(
|
||||
_root: RelativePath | undefined
|
||||
): Promise<RelativePath[]> {
|
||||
return ["file.md"];
|
||||
}
|
||||
public async read(_path: RelativePath): Promise<Uint8Array> {
|
||||
throw new Error("Method not implemented.");
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { SafeFileSystemOperations } from "./safe-filesystem-operations";
|
|||
import type { TextWithCursors } from "reconcile-text";
|
||||
import { isBinary, reconcile } from "reconcile-text";
|
||||
import { isFileTypeMergable } from "../utils/is-file-type-mergable";
|
||||
|
||||
export class FileOperations {
|
||||
private static readonly PARENTHESES_REGEX = / \((\d+)\)$/;
|
||||
private readonly fs: SafeFileSystemOperations;
|
||||
|
|
@ -18,8 +19,22 @@ export class FileOperations {
|
|||
this.fs = new SafeFileSystemOperations(fs, logger);
|
||||
}
|
||||
|
||||
public async listAllFiles(): Promise<RelativePath[]> {
|
||||
return this.fs.listAllFiles();
|
||||
private static getParentDirAndFile(
|
||||
path: RelativePath
|
||||
): [RelativePath, RelativePath] {
|
||||
const pathParts = path.split("/");
|
||||
const fileName = pathParts.pop();
|
||||
if (fileName == "" || fileName == null) {
|
||||
throw new Error(`Path '${path}' cannot be empty`);
|
||||
}
|
||||
|
||||
return [pathParts.join("/"), fileName];
|
||||
}
|
||||
|
||||
public async listFilesRecursively(
|
||||
root: RelativePath | undefined = undefined
|
||||
): Promise<RelativePath[]> {
|
||||
return this.fs.listFilesRecursively(root);
|
||||
}
|
||||
|
||||
public async read(path: RelativePath): Promise<Uint8Array> {
|
||||
|
|
@ -120,7 +135,8 @@ export class FileOperations {
|
|||
|
||||
public async delete(path: RelativePath): Promise<void> {
|
||||
if (await this.exists(path)) {
|
||||
return this.fs.delete(path);
|
||||
await this.fs.delete(path);
|
||||
await this.deletingEmptyParentDirectoriesOfDeletedFile(path);
|
||||
} else {
|
||||
this.logger.debug(`No need to delete '${path}', it doesn't exist`);
|
||||
}
|
||||
|
|
@ -146,6 +162,31 @@ export class FileOperations {
|
|||
|
||||
this.database.move(oldPath, newPath);
|
||||
await this.fs.rename(oldPath, newPath);
|
||||
await this.deletingEmptyParentDirectoriesOfDeletedFile(oldPath);
|
||||
}
|
||||
|
||||
private async deletingEmptyParentDirectoriesOfDeletedFile(
|
||||
path: RelativePath
|
||||
): Promise<void> {
|
||||
let directory = path;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
while (true) {
|
||||
[directory] = FileOperations.getParentDirAndFile(directory);
|
||||
if (directory.length === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
const remainingContent =
|
||||
await this.fs.listFilesRecursively(directory);
|
||||
if (remainingContent.length === 0) {
|
||||
this.logger.debug(
|
||||
`Folder (${directory}) is now empty, deleting`
|
||||
);
|
||||
await this.fs.delete(directory);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fromNativeLineEndings(content: Uint8Array): Uint8Array {
|
||||
|
|
@ -184,13 +225,9 @@ export class FileOperations {
|
|||
}
|
||||
|
||||
private async deconflictPath(path: RelativePath): Promise<RelativePath> {
|
||||
const pathParts = path.split("/");
|
||||
const fileName = pathParts.pop();
|
||||
if (fileName == "" || fileName == null) {
|
||||
throw new Error(`Path '${path}' cannot be empty`);
|
||||
}
|
||||
// eslint-disable-next-line prefer-const
|
||||
let [directory, fileName] = FileOperations.getParentDirAndFile(path);
|
||||
|
||||
let directory = pathParts.join("/");
|
||||
if (directory) {
|
||||
directory += "/";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ import type { RelativePath } from "../persistence/database";
|
|||
import type { TextWithCursors } from "reconcile-text";
|
||||
|
||||
export interface FileSystemOperations {
|
||||
// List all files that should be synced.
|
||||
listAllFiles: () => Promise<RelativePath[]>;
|
||||
// List all files under root that should be synced. If root is undefined, return every file.
|
||||
listFilesRecursively: (
|
||||
root: RelativePath | undefined
|
||||
) => Promise<RelativePath[]>;
|
||||
|
||||
// Read the content of a file.
|
||||
read: (path: RelativePath) => Promise<Uint8Array>;
|
||||
|
|
|
|||
|
|
@ -20,9 +20,11 @@ export class SafeFileSystemOperations implements FileSystemOperations {
|
|||
this.locks = new Locks(logger);
|
||||
}
|
||||
|
||||
public async listAllFiles(): Promise<RelativePath[]> {
|
||||
public async listFilesRecursively(
|
||||
root: RelativePath | undefined
|
||||
): Promise<RelativePath[]> {
|
||||
this.logger.debug("Listing all files");
|
||||
const result = await this.fs.listAllFiles();
|
||||
const result = await this.fs.listFilesRecursively(root);
|
||||
this.logger.debug(`Listed ${result.length} files`);
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -335,7 +335,7 @@ export class Syncer {
|
|||
private async internalScheduleSyncForOfflineChanges(): Promise<void> {
|
||||
await this.createFakeDocumentsFromRemoteState();
|
||||
|
||||
const allLocalFiles = await this.operations.listAllFiles();
|
||||
const allLocalFiles = await this.operations.listFilesRecursively();
|
||||
|
||||
let locallyPossiblyDeletedFiles: DocumentRecord[] = [];
|
||||
|
||||
|
|
@ -431,7 +431,7 @@ export class Syncer {
|
|||
}
|
||||
|
||||
const [allLocalFiles, remote] = await Promise.all([
|
||||
this.operations.listAllFiles(),
|
||||
this.operations.listFilesRecursively(),
|
||||
this.syncQueue.add(async () => this.syncService.getAll())
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ export class MockAgent extends MockClient {
|
|||
options.push(this.enableSyncAction.bind(this));
|
||||
}
|
||||
|
||||
const files = await this.listAllFiles();
|
||||
const files = await this.listFilesRecursively();
|
||||
|
||||
if (files.length > 0) {
|
||||
options.push(
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
SyncClient
|
||||
} from "sync-client";
|
||||
import type { TextWithCursors } from "reconcile-text";
|
||||
|
||||
export class MockClient implements FileSystemOperations {
|
||||
protected readonly localFiles = new Map<string, Uint8Array>();
|
||||
protected client!: SyncClient;
|
||||
|
|
@ -46,7 +47,9 @@ export class MockClient implements FileSystemOperations {
|
|||
await this.client.start();
|
||||
}
|
||||
|
||||
public async listAllFiles(): Promise<RelativePath[]> {
|
||||
public async listFilesRecursively(
|
||||
_root: RelativePath | undefined = undefined // we don't use multi-level paths during tests
|
||||
): Promise<RelativePath[]> {
|
||||
return Array.from(this.localFiles.keys());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ RUN apt update && \
|
|||
pkg-config && \
|
||||
cargo install sqlx-cli
|
||||
|
||||
# Build application
|
||||
COPY . .
|
||||
|
||||
RUN sqlx database create --database-url sqlite://db.sqlite3 && \
|
||||
|
|
@ -28,9 +27,6 @@ RUN apt update && \
|
|||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=builder /usr/src/backend/target/release/sync_server /app/sync_server
|
||||
COPY test-entrypoint.sh /app/test-entrypoint.sh
|
||||
|
||||
RUN chmod +x /app/test-entrypoint.sh
|
||||
|
||||
VOLUME /data
|
||||
EXPOSE 3000/tcp
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue