From 5df06bbed58c62938bcc9876daceaa87ee979de5 Mon Sep 17 00:00:00 2001 From: fyears <1142836+fyears@users.noreply.github.com> Date: Sun, 25 Feb 2024 22:18:14 +0800 Subject: [PATCH] fix mtime for webdav --- src/baseTypes.ts | 5 +++++ src/remote.ts | 3 ++- src/remoteForDropbox.ts | 18 ++++++++++++++---- src/remoteForOnedrive.ts | 18 ++++++++++++++---- src/remoteForS3.ts | 13 ++++++++++--- src/remoteForWebdav.ts | 21 +++++++++++++++------ src/sync.ts | 34 +++++++++++++++++++++++++++------- 7 files changed, 87 insertions(+), 25 deletions(-) diff --git a/src/baseTypes.ts b/src/baseTypes.ts index 81fd705..38024f4 100644 --- a/src/baseTypes.ts +++ b/src/baseTypes.ts @@ -185,6 +185,11 @@ export interface Entity { etag?: string; } +export interface UploadedType { + entity: Entity; + mtimeCli?: number; +} + /** * A replacement of FileOrFolderMixedState */ diff --git a/src/remote.ts b/src/remote.ts index 68e5570..c7467b1 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -6,6 +6,7 @@ import type { S3Config, SUPPORTED_SERVICES_TYPE, WebdavConfig, + UploadedType, } from "./baseTypes"; import * as dropbox from "./remoteForDropbox"; import * as onedrive from "./remoteForOnedrive"; @@ -112,7 +113,7 @@ export class RemoteClient { foldersCreatedBefore: Set | undefined = undefined, uploadRaw: boolean = false, rawContent: string | ArrayBuffer = "" - ) => { + ): Promise => { if (this.serviceType === "s3") { return await s3.uploadToRemote( s3.getS3Client(this.s3Config!), diff --git a/src/remoteForDropbox.ts b/src/remoteForDropbox.ts index 5052049..a084919 100644 --- a/src/remoteForDropbox.ts +++ b/src/remoteForDropbox.ts @@ -8,6 +8,7 @@ import { Entity, COMMAND_CALLBACK_DROPBOX, OAUTH2_FORCE_EXPIRE_MILLISECONDS, + UploadedType, } from "./baseTypes"; import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt"; import { @@ -460,7 +461,7 @@ export const uploadToRemote = async ( rawContent: string | ArrayBuffer = "", rawContentMTime: number = 0, rawContentCTime: number = 0 -) => { +): Promise => { await client.init(); let uploadFile = fileOrFolderPath; @@ -526,7 +527,10 @@ export const uploadToRemote = async ( } } const res = await getRemoteMeta(client, uploadFile); - return res; + return { + entity: res, + mtimeCli: mtime, + }; } else { // if encrypted, upload a fake file with the encrypted file name await retryReq( @@ -538,7 +542,10 @@ export const uploadToRemote = async ( }), fileOrFolderPath ); - return await getRemoteMeta(client, uploadFile); + return { + entity: await getRemoteMeta(client, uploadFile), + mtimeCli: mtime, + }; } } else { // file @@ -587,7 +594,10 @@ export const uploadToRemote = async ( foldersCreatedBefore?.add(dir); } } - return await getRemoteMeta(client, uploadFile); + return { + entity: await getRemoteMeta(client, uploadFile), + mtimeCli: mtime, + }; } }; diff --git a/src/remoteForOnedrive.ts b/src/remoteForOnedrive.ts index f2c64bc..52cbb4b 100644 --- a/src/remoteForOnedrive.ts +++ b/src/remoteForOnedrive.ts @@ -15,6 +15,7 @@ import { OAUTH2_FORCE_EXPIRE_MILLISECONDS, OnedriveConfig, Entity, + UploadedType, } from "./baseTypes"; import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt"; import { @@ -701,7 +702,7 @@ export const uploadToRemote = async ( foldersCreatedBefore: Set | undefined = undefined, uploadRaw: boolean = false, rawContent: string | ArrayBuffer = "" -) => { +): Promise => { await client.init(); let uploadFile = fileOrFolderPath; @@ -759,7 +760,10 @@ export const uploadToRemote = async ( await client.patchJson(uploadFile, k); } const res = await getRemoteMeta(client, uploadFile); - return res; + return { + entity: res, + mtimeCli: mtime, + }; } else { // if encrypted, // upload a fake, random-size file @@ -790,7 +794,10 @@ export const uploadToRemote = async ( } // log.info(uploadResult) const res = await getRemoteMeta(client, uploadFile); - return res; + return { + entity: res, + mtimeCli: mtime, + }; } } else { // file @@ -889,7 +896,10 @@ export const uploadToRemote = async ( } const res = await getRemoteMeta(client, uploadFile); - return res; + return { + entity: res, + mtimeCli: mtime, + }; } }; diff --git a/src/remoteForS3.ts b/src/remoteForS3.ts index b367b56..6d59406 100644 --- a/src/remoteForS3.ts +++ b/src/remoteForS3.ts @@ -30,6 +30,7 @@ import { DEFAULT_CONTENT_TYPE, Entity, S3Config, + UploadedType, VALID_REQURL, } from "./baseTypes"; import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt"; @@ -365,7 +366,7 @@ export const uploadToRemote = async ( rawContent: string | ArrayBuffer = "", rawContentMTime: number = 0, rawContentCTime: number = 0 -) => { +): Promise => { log.debug(`uploading ${fileOrFolderPath}`); let uploadFile = fileOrFolderPath; if (password !== "") { @@ -408,7 +409,10 @@ export const uploadToRemote = async ( }) ); const res = await getRemoteMeta(s3Client, s3Config, uploadFile); - return res; + return { + entity: res, + mtimeCli: mtime, + }; } else { // file // we ignore isRecursively parameter here @@ -476,7 +480,10 @@ export const uploadToRemote = async ( // log.debug( // `uploaded ${uploadFile} with res=${JSON.stringify(res, null, 2)}` // ); - return res; + return { + entity: res, + mtimeCli: mtime, + }; } }; diff --git a/src/remoteForWebdav.ts b/src/remoteForWebdav.ts index 6ad30cb..0630e34 100644 --- a/src/remoteForWebdav.ts +++ b/src/remoteForWebdav.ts @@ -5,7 +5,7 @@ import { Queue } from "@fyears/tsqueue"; import chunk from "lodash/chunk"; import flatten from "lodash/flatten"; 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 { bufferToArrayBuffer, getPathFolder, mkdirpInVault } from "./misc"; @@ -340,7 +340,7 @@ export const uploadToRemote = async ( remoteEncryptedKey: string = "", uploadRaw: boolean = false, rawContent: string | ArrayBuffer = "" -) => { +): Promise => { await client.init(); let uploadFile = fileOrFolderPath; if (password !== "") { @@ -368,7 +368,9 @@ export const uploadToRemote = async ( recursive: true, }); const res = await getRemoteMeta(client, uploadFile); - return res; + return { + entity: res, + }; } else { // if encrypted, upload a fake file with the encrypted file name 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 { // file // we ignore isRecursively parameter here - let localContent = undefined; + let localContent: ArrayBuffer | undefined = undefined; + let mtimeCli: number | undefined = undefined; if (uploadRaw) { if (typeof rawContent === "string") { localContent = new TextEncoder().encode(rawContent).buffer; @@ -397,6 +402,7 @@ export const uploadToRemote = async ( ); } localContent = await vault.adapter.readBinary(fileOrFolderPath); + mtimeCli = (await vault.adapter.stat(fileOrFolderPath))?.mtime; } let remoteContent = localContent; if (password !== "") { @@ -415,7 +421,10 @@ export const uploadToRemote = async ( }, }); - return await getRemoteMeta(client, uploadFile); + return { + entity: await getRemoteMeta(client, uploadFile), + mtimeCli: mtimeCli, + }; } }; diff --git a/src/sync.ts b/src/sync.ts index d0a3d2d..f09fccc 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -249,6 +249,24 @@ const decryptRemoteEntityInplace = async (remote: Entity, password: string) => { 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. * 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 local.sizeEnc = getSizeFromOrigToEnc(local.size); } - + if (local.keyEnc === undefined || local.keyEnc === "") { if ( 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. } else { // 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, vault, false, password, r.local!.keyEnc ); - await decryptRemoteEntityInplace(remoteObjMeta, password); - await upsertPrevSyncRecordByVault(db, vaultRandomID, remoteObjMeta); + await decryptRemoteEntityInplace(entity, password); + await fullfillMTimeOfRemoteEntityInplace(entity, mtimeCli); + await upsertPrevSyncRecordByVault(db, vaultRandomID, entity); } } else if ( r.decision === "modified_remote" || @@ -936,7 +955,7 @@ const dispatchOperationToActualV3 = async ( throw Error(`${r.decision} not implemented yet: ${JSON.stringify(r)}`); } else if (r.decision === "folder_to_be_created") { await mkdirpInVault(r.key, vault); - const remoteObjMeta = await client.uploadToRemote( + const { entity, mtimeCli } = await client.uploadToRemote( r.key, vault, false, @@ -944,8 +963,9 @@ const dispatchOperationToActualV3 = async ( r.local!.keyEnc ); // we need to decrypt the key!!! - await decryptRemoteEntityInplace(remoteObjMeta, password); - await upsertPrevSyncRecordByVault(db, vaultRandomID, remoteObjMeta); + await decryptRemoteEntityInplace(entity, password); + await fullfillMTimeOfRemoteEntityInplace(entity, mtimeCli); + await upsertPrevSyncRecordByVault(db, vaultRandomID, entity); } else if (r.decision === "folder_to_be_deleted") { await localDeleteFunc(r.key); await client.deleteFromRemote(r.key, password, r.remote!.keyEnc);