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;
|
||||
}
|
||||
|
||||
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
|
||||
* remote: x.md -> download to local -> using original name x.md
|
||||
*/
|
||||
export async function duplicateFile(
|
||||
async function tryDuplicateFileForDiffSizes(
|
||||
key: string,
|
||||
left: FakeFs,
|
||||
right: FakeFs,
|
||||
uploadCallback: (entity: Entity) => Promise<any>,
|
||||
downloadCallback: (entity: Entity) => Promise<any>
|
||||
key2: string,
|
||||
fsLocal: FakeFs,
|
||||
fsRemote: FakeFs,
|
||||
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 usable = false;
|
||||
do {
|
||||
try {
|
||||
const s = await left.stat(key2);
|
||||
const s = await fsLocal.stat(key2);
|
||||
if (s === null || s === undefined) {
|
||||
throw Error(`not exist $${key2}`);
|
||||
}
|
||||
@ -228,30 +347,31 @@ export async function duplicateFile(
|
||||
usable = true;
|
||||
}
|
||||
} while (!usable);
|
||||
await left.rename(key, key2);
|
||||
|
||||
/**
|
||||
* x.dup.md -> upload to remote
|
||||
*/
|
||||
async function f1() {
|
||||
const k = await copyFile(key2, left, right);
|
||||
await uploadCallback(k.entity);
|
||||
return k.entity;
|
||||
const localSize = await fsLocal.stat(key);
|
||||
const remoteSize = await fsRemote.stat(key);
|
||||
|
||||
if (
|
||||
localSize !== undefined &&
|
||||
remoteSize !== undefined &&
|
||||
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";
|
||||
import type { Profiler } from "../../src/profiler";
|
||||
import { checkProRunnableAndFixInplace } from "./account";
|
||||
import { duplicateFile, isMergable, mergeFile } from "./conflictLogic";
|
||||
import { isMergable, mergeFile, tryDuplicateFile } from "./conflictLogic";
|
||||
import {
|
||||
clearFileContentHistoryByVaultAndProfile,
|
||||
getFileContentHistoryByVaultAndProfile,
|
||||
@ -1351,27 +1351,31 @@ const dispatchOperationToActualV3 = async (
|
||||
r.key
|
||||
);
|
||||
const mtimeCli = (await fsLocal.stat(r.key)).mtimeCli!;
|
||||
const { upload, download } = await duplicateFile(
|
||||
await tryDuplicateFile(
|
||||
r.key,
|
||||
fsLocal,
|
||||
fsEncrypt,
|
||||
async (upload) => {
|
||||
// TODO: abstract away the dirty hack
|
||||
fullfillMTimeOfRemoteEntityInplace(upload, mtimeCli);
|
||||
await upsertPrevSyncRecordByVaultAndProfile(
|
||||
db,
|
||||
vaultRandomID,
|
||||
profileID,
|
||||
upload
|
||||
);
|
||||
if (upload !== undefined) {
|
||||
// TODO: abstract away the dirty hack
|
||||
fullfillMTimeOfRemoteEntityInplace(upload, mtimeCli);
|
||||
await upsertPrevSyncRecordByVaultAndProfile(
|
||||
db,
|
||||
vaultRandomID,
|
||||
profileID,
|
||||
upload
|
||||
);
|
||||
}
|
||||
},
|
||||
async (download) => {
|
||||
await upsertPrevSyncRecordByVaultAndProfile(
|
||||
db,
|
||||
vaultRandomID,
|
||||
profileID,
|
||||
download
|
||||
);
|
||||
if (download !== undefined) {
|
||||
await upsertPrevSyncRecordByVaultAndProfile(
|
||||
db,
|
||||
vaultRandomID,
|
||||
profileID,
|
||||
download
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user