more logic for potentially the same files in smart conflict

This commit is contained in:
fyears 2024-06-29 00:14:53 +08:00
parent 11c66d7030
commit e74e49538e
2 changed files with 170 additions and 46 deletions

View File

@ -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,
};
} }

View File

@ -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,11 +1351,12 @@ 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) => {
if (upload !== undefined) {
// TODO: abstract away the dirty hack // TODO: abstract away the dirty hack
fullfillMTimeOfRemoteEntityInplace(upload, mtimeCli); fullfillMTimeOfRemoteEntityInplace(upload, mtimeCli);
await upsertPrevSyncRecordByVaultAndProfile( await upsertPrevSyncRecordByVaultAndProfile(
@ -1364,8 +1365,10 @@ const dispatchOperationToActualV3 = async (
profileID, profileID,
upload upload
); );
}
}, },
async (download) => { async (download) => {
if (download !== undefined) {
await upsertPrevSyncRecordByVaultAndProfile( await upsertPrevSyncRecordByVaultAndProfile(
db, db,
vaultRandomID, vaultRandomID,
@ -1373,6 +1376,7 @@ const dispatchOperationToActualV3 = async (
download download
); );
} }
}
); );
} }
} else if (r.decision === "folder_to_be_created") { } else if (r.decision === "folder_to_be_created") {