more logic for potentially the same files in smart conflict
This commit is contained in:
parent
11c66d7030
commit
e74e49538e
@ -200,22 +200,141 @@ export function getFileRename(key: string) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function arraysAreEqual(arr1: ArrayBuffer, arr2: ArrayBuffer) {
|
||||||
|
if (arr1.byteLength !== arr2.byteLength) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const u1 = new Uint8Array(arr1);
|
||||||
|
const u2 = new Uint8Array(arr2);
|
||||||
|
|
||||||
|
for (let i = 0; i < u1.byteLength; ++i) {
|
||||||
|
if (u1[i] !== u2[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. download remote
|
||||||
|
* 2. compare
|
||||||
|
* 3. if the same, update local but not upload
|
||||||
|
* 4. if not the same, rename local and save remote
|
||||||
|
*/
|
||||||
|
async function tryDuplicateFileForSameSizes(
|
||||||
|
key: string,
|
||||||
|
key2: string,
|
||||||
|
fsLocal: FakeFs,
|
||||||
|
fsRemote: FakeFs,
|
||||||
|
uploadCallback: (entity: Entity | undefined) => Promise<any>,
|
||||||
|
downloadCallback: (entity: Entity | undefined) => Promise<any>
|
||||||
|
) {
|
||||||
|
console.debug(`tryDuplicateFileForSameSizes: ${key}`);
|
||||||
|
|
||||||
|
// 1. download
|
||||||
|
const remoteContent = await fsRemote.readFile(key);
|
||||||
|
|
||||||
|
// 2. compare
|
||||||
|
const localContent = await fsLocal.readFile(key);
|
||||||
|
const eq = arraysAreEqual(localContent, remoteContent);
|
||||||
|
|
||||||
|
if (eq) {
|
||||||
|
// 3. if the same, update local but not upload
|
||||||
|
// read meta of remote, as if we have downloaded the file
|
||||||
|
console.debug(`tryDuplicateFileForSameSizes: ${key} content equal`);
|
||||||
|
const entityRemote = await fsRemote.stat(key);
|
||||||
|
|
||||||
|
// write
|
||||||
|
const downloadResultEntity = await fsLocal.writeFile(
|
||||||
|
key,
|
||||||
|
remoteContent,
|
||||||
|
entityRemote.mtimeCli ?? Date.now(),
|
||||||
|
entityRemote.mtimeCli ?? Date.now()
|
||||||
|
);
|
||||||
|
await downloadCallback(downloadResultEntity);
|
||||||
|
|
||||||
|
// no uploadCallback here
|
||||||
|
} else {
|
||||||
|
// 4. if not the same, rename local and save remote
|
||||||
|
console.debug(`tryDuplicateFileForSameSizes: ${key} content not equal`);
|
||||||
|
|
||||||
|
await fsLocal.rename(key, key2);
|
||||||
|
|
||||||
|
const entityRemote = await fsRemote.stat(key);
|
||||||
|
const downloadResultEntity = await fsLocal.writeFile(
|
||||||
|
key,
|
||||||
|
remoteContent,
|
||||||
|
entityRemote.mtimeCli ?? Date.now(),
|
||||||
|
entityRemote.mtimeCli ?? Date.now()
|
||||||
|
);
|
||||||
|
await downloadCallback(downloadResultEntity);
|
||||||
|
|
||||||
|
const entityLocal = await fsLocal.stat(key2); // key2 here!
|
||||||
|
const uploadResultEntity = await fsRemote.writeFile(
|
||||||
|
key2, // key2 here!
|
||||||
|
localContent,
|
||||||
|
entityLocal.mtimeCli ?? Date.now(),
|
||||||
|
entityLocal.mtimeCli ?? Date.now()
|
||||||
|
);
|
||||||
|
await uploadCallback(uploadResultEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* local: x.md -> x.dup.md -> upload to remote
|
* local: x.md -> x.dup.md -> upload to remote
|
||||||
* remote: x.md -> download to local -> using original name x.md
|
* remote: x.md -> download to local -> using original name x.md
|
||||||
*/
|
*/
|
||||||
export async function duplicateFile(
|
async function tryDuplicateFileForDiffSizes(
|
||||||
key: string,
|
key: string,
|
||||||
left: FakeFs,
|
key2: string,
|
||||||
right: FakeFs,
|
fsLocal: FakeFs,
|
||||||
uploadCallback: (entity: Entity) => Promise<any>,
|
fsRemote: FakeFs,
|
||||||
downloadCallback: (entity: Entity) => Promise<any>
|
uploadCallback: (entity: Entity | undefined) => Promise<any>,
|
||||||
|
downloadCallback: (entity: Entity | undefined) => Promise<any>
|
||||||
|
) {
|
||||||
|
console.debug(`tryDuplicateFileForDiffSizes: ${key}`);
|
||||||
|
|
||||||
|
await fsLocal.rename(key, key2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* x.dup.md -> upload to remote
|
||||||
|
*/
|
||||||
|
async function f1() {
|
||||||
|
const k = await copyFile(key2, fsLocal, fsRemote);
|
||||||
|
await uploadCallback(k.entity);
|
||||||
|
return k.entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* x.md -> download to local
|
||||||
|
*/
|
||||||
|
async function f2() {
|
||||||
|
const k = await copyFile(key, fsRemote, fsLocal);
|
||||||
|
await downloadCallback(k.entity);
|
||||||
|
return k.entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [resUpload, resDownload] = await Promise.all([f1(), f2()]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
upload: resUpload,
|
||||||
|
download: resDownload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function tryDuplicateFile(
|
||||||
|
key: string,
|
||||||
|
fsLocal: FakeFs,
|
||||||
|
fsRemote: FakeFs,
|
||||||
|
uploadCallback: (entity: Entity | undefined) => Promise<any>,
|
||||||
|
downloadCallback: (entity: Entity | undefined) => Promise<any>
|
||||||
) {
|
) {
|
||||||
let key2 = getFileRename(key);
|
let key2 = getFileRename(key);
|
||||||
let usable = false;
|
let usable = false;
|
||||||
do {
|
do {
|
||||||
try {
|
try {
|
||||||
const s = await left.stat(key2);
|
const s = await fsLocal.stat(key2);
|
||||||
if (s === null || s === undefined) {
|
if (s === null || s === undefined) {
|
||||||
throw Error(`not exist $${key2}`);
|
throw Error(`not exist $${key2}`);
|
||||||
}
|
}
|
||||||
@ -228,30 +347,31 @@ export async function duplicateFile(
|
|||||||
usable = true;
|
usable = true;
|
||||||
}
|
}
|
||||||
} while (!usable);
|
} while (!usable);
|
||||||
await left.rename(key, key2);
|
|
||||||
|
|
||||||
/**
|
const localSize = await fsLocal.stat(key);
|
||||||
* x.dup.md -> upload to remote
|
const remoteSize = await fsRemote.stat(key);
|
||||||
*/
|
|
||||||
async function f1() {
|
if (
|
||||||
const k = await copyFile(key2, left, right);
|
localSize !== undefined &&
|
||||||
await uploadCallback(k.entity);
|
remoteSize !== undefined &&
|
||||||
return k.entity;
|
localSize.sizeRaw === remoteSize.sizeRaw
|
||||||
|
) {
|
||||||
|
return await tryDuplicateFileForSameSizes(
|
||||||
|
key,
|
||||||
|
key2,
|
||||||
|
fsLocal,
|
||||||
|
fsRemote,
|
||||||
|
uploadCallback,
|
||||||
|
downloadCallback
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return await tryDuplicateFileForDiffSizes(
|
||||||
|
key,
|
||||||
|
key2,
|
||||||
|
fsLocal,
|
||||||
|
fsRemote,
|
||||||
|
uploadCallback,
|
||||||
|
downloadCallback
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* x.md -> download to local
|
|
||||||
*/
|
|
||||||
async function f2() {
|
|
||||||
const k = await copyFile(key, right, left);
|
|
||||||
await downloadCallback(k.entity);
|
|
||||||
return k.entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [resUpload, resDownload] = await Promise.all([f1(), f2()]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
upload: resUpload,
|
|
||||||
download: resDownload,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,7 +36,7 @@ import {
|
|||||||
} from "../../src/misc";
|
} from "../../src/misc";
|
||||||
import type { Profiler } from "../../src/profiler";
|
import type { Profiler } from "../../src/profiler";
|
||||||
import { checkProRunnableAndFixInplace } from "./account";
|
import { checkProRunnableAndFixInplace } from "./account";
|
||||||
import { duplicateFile, isMergable, mergeFile } from "./conflictLogic";
|
import { isMergable, mergeFile, tryDuplicateFile } from "./conflictLogic";
|
||||||
import {
|
import {
|
||||||
clearFileContentHistoryByVaultAndProfile,
|
clearFileContentHistoryByVaultAndProfile,
|
||||||
getFileContentHistoryByVaultAndProfile,
|
getFileContentHistoryByVaultAndProfile,
|
||||||
@ -1351,27 +1351,31 @@ const dispatchOperationToActualV3 = async (
|
|||||||
r.key
|
r.key
|
||||||
);
|
);
|
||||||
const mtimeCli = (await fsLocal.stat(r.key)).mtimeCli!;
|
const mtimeCli = (await fsLocal.stat(r.key)).mtimeCli!;
|
||||||
const { upload, download } = await duplicateFile(
|
await tryDuplicateFile(
|
||||||
r.key,
|
r.key,
|
||||||
fsLocal,
|
fsLocal,
|
||||||
fsEncrypt,
|
fsEncrypt,
|
||||||
async (upload) => {
|
async (upload) => {
|
||||||
// TODO: abstract away the dirty hack
|
if (upload !== undefined) {
|
||||||
fullfillMTimeOfRemoteEntityInplace(upload, mtimeCli);
|
// TODO: abstract away the dirty hack
|
||||||
await upsertPrevSyncRecordByVaultAndProfile(
|
fullfillMTimeOfRemoteEntityInplace(upload, mtimeCli);
|
||||||
db,
|
await upsertPrevSyncRecordByVaultAndProfile(
|
||||||
vaultRandomID,
|
db,
|
||||||
profileID,
|
vaultRandomID,
|
||||||
upload
|
profileID,
|
||||||
);
|
upload
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async (download) => {
|
async (download) => {
|
||||||
await upsertPrevSyncRecordByVaultAndProfile(
|
if (download !== undefined) {
|
||||||
db,
|
await upsertPrevSyncRecordByVaultAndProfile(
|
||||||
vaultRandomID,
|
db,
|
||||||
profileID,
|
vaultRandomID,
|
||||||
download
|
profileID,
|
||||||
);
|
download
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user