fix mtime for webdav

This commit is contained in:
fyears 2024-02-25 22:18:14 +08:00
parent 1f4737bfb8
commit 5df06bbed5
7 changed files with 87 additions and 25 deletions

View File

@ -185,6 +185,11 @@ export interface Entity {
etag?: string; etag?: string;
} }
export interface UploadedType {
entity: Entity;
mtimeCli?: number;
}
/** /**
* A replacement of FileOrFolderMixedState * A replacement of FileOrFolderMixedState
*/ */

View File

@ -6,6 +6,7 @@ import type {
S3Config, S3Config,
SUPPORTED_SERVICES_TYPE, SUPPORTED_SERVICES_TYPE,
WebdavConfig, WebdavConfig,
UploadedType,
} from "./baseTypes"; } from "./baseTypes";
import * as dropbox from "./remoteForDropbox"; import * as dropbox from "./remoteForDropbox";
import * as onedrive from "./remoteForOnedrive"; import * as onedrive from "./remoteForOnedrive";
@ -112,7 +113,7 @@ export class RemoteClient {
foldersCreatedBefore: Set<string> | undefined = undefined, foldersCreatedBefore: Set<string> | undefined = undefined,
uploadRaw: boolean = false, uploadRaw: boolean = false,
rawContent: string | ArrayBuffer = "" rawContent: string | ArrayBuffer = ""
) => { ): Promise<UploadedType> => {
if (this.serviceType === "s3") { if (this.serviceType === "s3") {
return await s3.uploadToRemote( return await s3.uploadToRemote(
s3.getS3Client(this.s3Config!), s3.getS3Client(this.s3Config!),

View File

@ -8,6 +8,7 @@ import {
Entity, Entity,
COMMAND_CALLBACK_DROPBOX, COMMAND_CALLBACK_DROPBOX,
OAUTH2_FORCE_EXPIRE_MILLISECONDS, OAUTH2_FORCE_EXPIRE_MILLISECONDS,
UploadedType,
} from "./baseTypes"; } from "./baseTypes";
import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt"; import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt";
import { import {
@ -460,7 +461,7 @@ export const uploadToRemote = async (
rawContent: string | ArrayBuffer = "", rawContent: string | ArrayBuffer = "",
rawContentMTime: number = 0, rawContentMTime: number = 0,
rawContentCTime: number = 0 rawContentCTime: number = 0
) => { ): Promise<UploadedType> => {
await client.init(); await client.init();
let uploadFile = fileOrFolderPath; let uploadFile = fileOrFolderPath;
@ -526,7 +527,10 @@ export const uploadToRemote = async (
} }
} }
const res = await getRemoteMeta(client, uploadFile); const res = await getRemoteMeta(client, uploadFile);
return res; return {
entity: res,
mtimeCli: mtime,
};
} else { } else {
// if encrypted, upload a fake file with the encrypted file name // if encrypted, upload a fake file with the encrypted file name
await retryReq( await retryReq(
@ -538,7 +542,10 @@ export const uploadToRemote = async (
}), }),
fileOrFolderPath fileOrFolderPath
); );
return await getRemoteMeta(client, uploadFile); return {
entity: await getRemoteMeta(client, uploadFile),
mtimeCli: mtime,
};
} }
} else { } else {
// file // file
@ -587,7 +594,10 @@ export const uploadToRemote = async (
foldersCreatedBefore?.add(dir); foldersCreatedBefore?.add(dir);
} }
} }
return await getRemoteMeta(client, uploadFile); return {
entity: await getRemoteMeta(client, uploadFile),
mtimeCli: mtime,
};
} }
}; };

View File

@ -15,6 +15,7 @@ import {
OAUTH2_FORCE_EXPIRE_MILLISECONDS, OAUTH2_FORCE_EXPIRE_MILLISECONDS,
OnedriveConfig, OnedriveConfig,
Entity, Entity,
UploadedType,
} from "./baseTypes"; } from "./baseTypes";
import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt"; import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt";
import { import {
@ -701,7 +702,7 @@ export const uploadToRemote = async (
foldersCreatedBefore: Set<string> | undefined = undefined, foldersCreatedBefore: Set<string> | undefined = undefined,
uploadRaw: boolean = false, uploadRaw: boolean = false,
rawContent: string | ArrayBuffer = "" rawContent: string | ArrayBuffer = ""
) => { ): Promise<UploadedType> => {
await client.init(); await client.init();
let uploadFile = fileOrFolderPath; let uploadFile = fileOrFolderPath;
@ -759,7 +760,10 @@ export const uploadToRemote = async (
await client.patchJson(uploadFile, k); await client.patchJson(uploadFile, k);
} }
const res = await getRemoteMeta(client, uploadFile); const res = await getRemoteMeta(client, uploadFile);
return res; return {
entity: res,
mtimeCli: mtime,
};
} else { } else {
// if encrypted, // if encrypted,
// upload a fake, random-size file // upload a fake, random-size file
@ -790,7 +794,10 @@ export const uploadToRemote = async (
} }
// log.info(uploadResult) // log.info(uploadResult)
const res = await getRemoteMeta(client, uploadFile); const res = await getRemoteMeta(client, uploadFile);
return res; return {
entity: res,
mtimeCli: mtime,
};
} }
} else { } else {
// file // file
@ -889,7 +896,10 @@ export const uploadToRemote = async (
} }
const res = await getRemoteMeta(client, uploadFile); const res = await getRemoteMeta(client, uploadFile);
return res; return {
entity: res,
mtimeCli: mtime,
};
} }
}; };

View File

@ -30,6 +30,7 @@ import {
DEFAULT_CONTENT_TYPE, DEFAULT_CONTENT_TYPE,
Entity, Entity,
S3Config, S3Config,
UploadedType,
VALID_REQURL, VALID_REQURL,
} from "./baseTypes"; } from "./baseTypes";
import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt"; import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt";
@ -365,7 +366,7 @@ export const uploadToRemote = async (
rawContent: string | ArrayBuffer = "", rawContent: string | ArrayBuffer = "",
rawContentMTime: number = 0, rawContentMTime: number = 0,
rawContentCTime: number = 0 rawContentCTime: number = 0
) => { ): Promise<UploadedType> => {
log.debug(`uploading ${fileOrFolderPath}`); log.debug(`uploading ${fileOrFolderPath}`);
let uploadFile = fileOrFolderPath; let uploadFile = fileOrFolderPath;
if (password !== "") { if (password !== "") {
@ -408,7 +409,10 @@ export const uploadToRemote = async (
}) })
); );
const res = await getRemoteMeta(s3Client, s3Config, uploadFile); const res = await getRemoteMeta(s3Client, s3Config, uploadFile);
return res; return {
entity: res,
mtimeCli: mtime,
};
} else { } else {
// file // file
// we ignore isRecursively parameter here // we ignore isRecursively parameter here
@ -476,7 +480,10 @@ export const uploadToRemote = async (
// log.debug( // log.debug(
// `uploaded ${uploadFile} with res=${JSON.stringify(res, null, 2)}` // `uploaded ${uploadFile} with res=${JSON.stringify(res, null, 2)}`
// ); // );
return res; return {
entity: res,
mtimeCli: mtime,
};
} }
}; };

View File

@ -5,7 +5,7 @@ import { Queue } from "@fyears/tsqueue";
import chunk from "lodash/chunk"; import chunk from "lodash/chunk";
import flatten from "lodash/flatten"; import flatten from "lodash/flatten";
import { getReasonPhrase } from "http-status-codes"; import { getReasonPhrase } from "http-status-codes";
import { Entity, VALID_REQURL, WebdavConfig } from "./baseTypes"; import { Entity, UploadedType, VALID_REQURL, WebdavConfig } from "./baseTypes";
import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt"; import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt";
import { bufferToArrayBuffer, getPathFolder, mkdirpInVault } from "./misc"; import { bufferToArrayBuffer, getPathFolder, mkdirpInVault } from "./misc";
@ -340,7 +340,7 @@ export const uploadToRemote = async (
remoteEncryptedKey: string = "", remoteEncryptedKey: string = "",
uploadRaw: boolean = false, uploadRaw: boolean = false,
rawContent: string | ArrayBuffer = "" rawContent: string | ArrayBuffer = ""
) => { ): Promise<UploadedType> => {
await client.init(); await client.init();
let uploadFile = fileOrFolderPath; let uploadFile = fileOrFolderPath;
if (password !== "") { if (password !== "") {
@ -368,7 +368,9 @@ export const uploadToRemote = async (
recursive: true, recursive: true,
}); });
const res = await getRemoteMeta(client, uploadFile); const res = await getRemoteMeta(client, uploadFile);
return res; return {
entity: res,
};
} else { } else {
// if encrypted, upload a fake file with the encrypted file name // if encrypted, upload a fake file with the encrypted file name
await client.client.putFileContents(uploadFile, "", { await client.client.putFileContents(uploadFile, "", {
@ -378,12 +380,15 @@ export const uploadToRemote = async (
}, },
}); });
return await getRemoteMeta(client, uploadFile); return {
entity: await getRemoteMeta(client, uploadFile),
};
} }
} else { } else {
// file // file
// we ignore isRecursively parameter here // we ignore isRecursively parameter here
let localContent = undefined; let localContent: ArrayBuffer | undefined = undefined;
let mtimeCli: number | undefined = undefined;
if (uploadRaw) { if (uploadRaw) {
if (typeof rawContent === "string") { if (typeof rawContent === "string") {
localContent = new TextEncoder().encode(rawContent).buffer; localContent = new TextEncoder().encode(rawContent).buffer;
@ -397,6 +402,7 @@ export const uploadToRemote = async (
); );
} }
localContent = await vault.adapter.readBinary(fileOrFolderPath); localContent = await vault.adapter.readBinary(fileOrFolderPath);
mtimeCli = (await vault.adapter.stat(fileOrFolderPath))?.mtime;
} }
let remoteContent = localContent; let remoteContent = localContent;
if (password !== "") { if (password !== "") {
@ -415,7 +421,10 @@ export const uploadToRemote = async (
}, },
}); });
return await getRemoteMeta(client, uploadFile); return {
entity: await getRemoteMeta(client, uploadFile),
mtimeCli: mtimeCli,
};
} }
}; };

View File

@ -249,6 +249,24 @@ const decryptRemoteEntityInplace = async (remote: Entity, password: string) => {
return remote; return remote;
}; };
const fullfillMTimeOfRemoteEntityInplace = (
remote: Entity,
mtimeCli?: number
) => {
if (
mtimeCli !== undefined &&
mtimeCli > 0 &&
(remote.mtimeCli === undefined ||
remote.mtimeCli <= 0 ||
(remote.mtimeSvr !== undefined &&
remote.mtimeSvr > 0 &&
remote.mtimeCli >= remote.mtimeSvr))
) {
remote.mtimeCli = mtimeCli;
}
return remote;
};
/** /**
* Directly throw error here. * Directly throw error here.
* We can only defer the checking now, because before decryption we don't know whether it's a file or folder. * We can only defer the checking now, because before decryption we don't know whether it's a file or folder.
@ -312,7 +330,7 @@ const encryptLocalEntityInplace = async (
// but local.key should always have value // but local.key should always have value
local.sizeEnc = getSizeFromOrigToEnc(local.size); local.sizeEnc = getSizeFromOrigToEnc(local.size);
} }
if (local.keyEnc === undefined || local.keyEnc === "") { if (local.keyEnc === undefined || local.keyEnc === "") {
if ( if (
remoteKeyEnc !== undefined && remoteKeyEnc !== undefined &&
@ -895,15 +913,16 @@ const dispatchOperationToActualV3 = async (
// if it's empty folder, or it's encrypted file/folder, it continues to be uploaded. // if it's empty folder, or it's encrypted file/folder, it continues to be uploaded.
} else { } else {
// log.debug(`before upload in sync, r=${JSON.stringify(r, null, 2)}`); // log.debug(`before upload in sync, r=${JSON.stringify(r, null, 2)}`);
const remoteObjMeta = await client.uploadToRemote( const { entity, mtimeCli } = await client.uploadToRemote(
r.key, r.key,
vault, vault,
false, false,
password, password,
r.local!.keyEnc r.local!.keyEnc
); );
await decryptRemoteEntityInplace(remoteObjMeta, password); await decryptRemoteEntityInplace(entity, password);
await upsertPrevSyncRecordByVault(db, vaultRandomID, remoteObjMeta); await fullfillMTimeOfRemoteEntityInplace(entity, mtimeCli);
await upsertPrevSyncRecordByVault(db, vaultRandomID, entity);
} }
} else if ( } else if (
r.decision === "modified_remote" || r.decision === "modified_remote" ||
@ -936,7 +955,7 @@ const dispatchOperationToActualV3 = async (
throw Error(`${r.decision} not implemented yet: ${JSON.stringify(r)}`); throw Error(`${r.decision} not implemented yet: ${JSON.stringify(r)}`);
} else if (r.decision === "folder_to_be_created") { } else if (r.decision === "folder_to_be_created") {
await mkdirpInVault(r.key, vault); await mkdirpInVault(r.key, vault);
const remoteObjMeta = await client.uploadToRemote( const { entity, mtimeCli } = await client.uploadToRemote(
r.key, r.key,
vault, vault,
false, false,
@ -944,8 +963,9 @@ const dispatchOperationToActualV3 = async (
r.local!.keyEnc r.local!.keyEnc
); );
// we need to decrypt the key!!! // we need to decrypt the key!!!
await decryptRemoteEntityInplace(remoteObjMeta, password); await decryptRemoteEntityInplace(entity, password);
await upsertPrevSyncRecordByVault(db, vaultRandomID, remoteObjMeta); await fullfillMTimeOfRemoteEntityInplace(entity, mtimeCli);
await upsertPrevSyncRecordByVault(db, vaultRandomID, entity);
} else if (r.decision === "folder_to_be_deleted") { } else if (r.decision === "folder_to_be_deleted") {
await localDeleteFunc(r.key); await localDeleteFunc(r.key);
await client.deleteFromRemote(r.key, password, r.remote!.keyEnc); await client.deleteFromRemote(r.key, password, r.remote!.keyEnc);