Fix deconflicting
This commit is contained in:
parent
becd7c222e
commit
a7b518d7ea
3 changed files with 32 additions and 8 deletions
|
|
@ -2,7 +2,7 @@ import type { FileSystemOperations } from "sync-client";
|
||||||
import type { Database, RelativePath } from "../persistence/database";
|
import type { Database, RelativePath } from "../persistence/database";
|
||||||
import { FileOperations } from "./file-operations";
|
import { FileOperations } from "./file-operations";
|
||||||
import { Logger } from "../tracing/logger";
|
import { Logger } from "../tracing/logger";
|
||||||
import { assertSetContainsExactly } from "src/utils/assert-set-contains-exactly";
|
import { assertSetContainsExactly } from "../utils/assert-set-contains-exactly";
|
||||||
|
|
||||||
describe("File operations", () => {
|
describe("File operations", () => {
|
||||||
class MockDatabase {
|
class MockDatabase {
|
||||||
|
|
@ -95,6 +95,22 @@ describe("File operations", () => {
|
||||||
await fileOperations.create("c.md", new Uint8Array());
|
await fileOperations.create("c.md", new Uint8Array());
|
||||||
await fileOperations.move("c.md", "b.md");
|
await fileOperations.move("c.md", "b.md");
|
||||||
assertSetContainsExactly(fs.names, "b.md", "b (1).md");
|
assertSetContainsExactly(fs.names, "b.md", "b (1).md");
|
||||||
|
|
||||||
|
await fileOperations.create("d.md", new Uint8Array());
|
||||||
|
await fileOperations.move("d.md", "b.md");
|
||||||
|
assertSetContainsExactly(fs.names, "b.md", "b (1).md", "b (2).md");
|
||||||
|
|
||||||
|
await fileOperations.create("file-23.md", new Uint8Array());
|
||||||
|
await fileOperations.create("file-23 (1).md", new Uint8Array());
|
||||||
|
await fileOperations.move("file-23.md", "file-23 (1).md");
|
||||||
|
assertSetContainsExactly(
|
||||||
|
fs.names,
|
||||||
|
"b.md",
|
||||||
|
"b (1).md",
|
||||||
|
"b (2).md",
|
||||||
|
"file-23 (1).md",
|
||||||
|
"file-23 (2).md"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should deconflict renames with paths", async () => {
|
test("should deconflict renames with paths", async () => {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
import type { Logger } from "src/tracing/logger";
|
import type { Logger } from "src/tracing/logger";
|
||||||
import type { FileSystemOperations } from "./filesystem-operations";
|
import type { FileSystemOperations } from "./filesystem-operations";
|
||||||
import type { RelativePath } from "src/persistence/database";
|
import type { Database, RelativePath } from "src/persistence/database";
|
||||||
import { isBinary, isFileTypeMergable, mergeText } from "sync_lib";
|
import { isBinary, isFileTypeMergable, mergeText } from "sync_lib";
|
||||||
import { SafeFileSystemOperations } from "./safe-filesystem-operations";
|
import { SafeFileSystemOperations } from "./safe-filesystem-operations";
|
||||||
|
|
||||||
export class FileOperations {
|
export class FileOperations {
|
||||||
private readonly fs: SafeFileSystemOperations;
|
private readonly fs: SafeFileSystemOperations;
|
||||||
|
private static readonly PARENTHESES_REGEX = / \((\d+)\)$/;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
|
private readonly database: Database,
|
||||||
fs: FileSystemOperations
|
fs: FileSystemOperations
|
||||||
) {
|
) {
|
||||||
this.fs = new SafeFileSystemOperations(fs);
|
this.fs = new SafeFileSystemOperations(fs);
|
||||||
|
|
@ -134,9 +136,10 @@ export class FileOperations {
|
||||||
if (await this.fs.exists(newPath)) {
|
if (await this.fs.exists(newPath)) {
|
||||||
const deconflictedPath = await this.deconflictPath(newPath);
|
const deconflictedPath = await this.deconflictPath(newPath);
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`Conflict when moving '${oldPath}' to '${newPath}', '${newPath}' already exists, deconflicting by moving it to '${deconflictedPath}'`
|
`Conflict when moving '${oldPath}' to '${newPath}', latter already exists, deconflicting by moving it to '${deconflictedPath}'`
|
||||||
);
|
);
|
||||||
this.fs.rename(newPath, deconflictedPath);
|
await this.database.updatePath(newPath, deconflictedPath);
|
||||||
|
await this.fs.rename(newPath, deconflictedPath);
|
||||||
} else {
|
} else {
|
||||||
await this.createParentDirectories(newPath);
|
await this.createParentDirectories(newPath);
|
||||||
}
|
}
|
||||||
|
|
@ -163,7 +166,10 @@ export class FileOperations {
|
||||||
|
|
||||||
private async deconflictPath(path: RelativePath): Promise<RelativePath> {
|
private async deconflictPath(path: RelativePath): Promise<RelativePath> {
|
||||||
const pathParts = path.split("/");
|
const pathParts = path.split("/");
|
||||||
const fileName = pathParts.pop()!;
|
const fileName = pathParts.pop();
|
||||||
|
if (fileName == "" || fileName == null) {
|
||||||
|
throw new Error(`Path '${path}' cannot be empty`);
|
||||||
|
}
|
||||||
|
|
||||||
let directory = pathParts.join("/");
|
let directory = pathParts.join("/");
|
||||||
if (directory) {
|
if (directory) {
|
||||||
|
|
@ -173,11 +179,13 @@ export class FileOperations {
|
||||||
const nameParts = fileName.split(".");
|
const nameParts = fileName.split(".");
|
||||||
const extension =
|
const extension =
|
||||||
nameParts.length > 1 ? "." + nameParts[nameParts.length - 1] : "";
|
nameParts.length > 1 ? "." + nameParts[nameParts.length - 1] : "";
|
||||||
const stem = extension ? nameParts.slice(0, -1).join(".") : fileName;
|
let stem = extension ? nameParts.slice(0, -1).join(".") : fileName;
|
||||||
let currentCount = Number.parseInt(
|
let currentCount = Number.parseInt(
|
||||||
/ \((\d+)\)$/.exec(stem)?.groups?.[0] ?? "0"
|
FileOperations.PARENTHESES_REGEX.exec(stem)?.groups?.[0] ?? "0"
|
||||||
);
|
);
|
||||||
|
stem = stem.replace(FileOperations.PARENTHESES_REGEX, "");
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
while (true) {
|
while (true) {
|
||||||
const newName =
|
const newName =
|
||||||
currentCount === 0
|
currentCount === 0
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ export class SyncClient {
|
||||||
database,
|
database,
|
||||||
settings,
|
settings,
|
||||||
syncService,
|
syncService,
|
||||||
new FileOperations(logger, fs),
|
new FileOperations(logger, database, fs),
|
||||||
history
|
history
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue