From 8dde6c59d2acf2a9977fa6e108e45a593b7e2862 Mon Sep 17 00:00:00 2001 From: fyears <1142836+fyears@users.noreply.github.com> Date: Sun, 14 Jan 2024 22:13:10 +0800 Subject: [PATCH] strict anywhere --- src/baseTypes.ts | 3 +- src/importExport.ts | 2 +- src/localdb.ts | 14 ++-- src/main.ts | 82 ++++++++++++++--------- src/metadataOnRemote.ts | 4 +- src/misc.ts | 17 +++-- src/obsFolderLister.ts | 9 ++- src/remote.ts | 80 +++++++++++------------ src/remoteForDropbox.ts | 30 ++++++--- src/remoteForOnedrive.ts | 38 ++++++++--- src/remoteForS3.ts | 55 ++++++++++------ src/remoteForWebdav.ts | 31 +++++---- src/settings.ts | 34 +++++----- src/sync.ts | 115 ++++++++++++++++++--------------- tests/metadataOnRemote.test.ts | 4 +- tsconfig.json | 1 + 16 files changed, 306 insertions(+), 213 deletions(-) diff --git a/src/baseTypes.ts b/src/baseTypes.ts index 3dd9e16..2a55e4d 100644 --- a/src/baseTypes.ts +++ b/src/baseTypes.ts @@ -48,6 +48,7 @@ export interface DropboxConfig { export type WebdavAuthType = "digest" | "basic"; export type WebdavDepthType = + | "auto" | "auto_unknown" | "auto_1" | "auto_infinity" @@ -112,7 +113,7 @@ export interface RemotelySavePluginSettings { export interface RemoteItem { key: string; - lastModified: number; + lastModified?: number; size: number; remoteType: SUPPORTED_SERVICES_TYPE; etag?: string; diff --git a/src/importExport.ts b/src/importExport.ts index 79c4e8a..74dfc5e 100644 --- a/src/importExport.ts +++ b/src/importExport.ts @@ -14,7 +14,7 @@ export const exportQrCodeUri = async ( currentVaultName: string, pluginVersion: string ) => { - const settings2 = cloneDeep(settings); + const settings2: Partial = cloneDeep(settings); delete settings2.dropbox; delete settings2.onedrive; delete settings2.vaultRandomID; diff --git a/src/localdb.ts b/src/localdb.ts index bea4029..4ed4ffa 100644 --- a/src/localdb.ts +++ b/src/localdb.ts @@ -436,8 +436,8 @@ export const insertRenameRecordByVault = async ( vaultRandomID: string ) => { // log.info(fileOrFolder); - let k1: FileFolderHistoryRecord; - let k2: FileFolderHistoryRecord; + let k1: FileFolderHistoryRecord | undefined; + let k2: FileFolderHistoryRecord | undefined; const actionWhen = Date.now(); if (fileOrFolder instanceof TFile) { k1 = { @@ -473,8 +473,10 @@ export const insertRenameRecordByVault = async ( // TAbstractFile does not contain these info // but from API_VER_STAT_FOLDER we can manually stat them by path. const s = await statFix(fileOrFolder.vault, fileOrFolder.path); - ctime = s.ctime; - mtime = s.mtime; + if (s !== undefined && s !== null) { + ctime = s.ctime; + mtime = s.mtime; + } } k1 = { key: key, @@ -500,8 +502,8 @@ export const insertRenameRecordByVault = async ( }; } await Promise.all([ - db.fileHistoryTbl.setItem(`${vaultRandomID}\t${k1.key}`, k1), - db.fileHistoryTbl.setItem(`${vaultRandomID}\t${k2.key}`, k2), + db.fileHistoryTbl.setItem(`${vaultRandomID}\t${k1!.key}`, k1), + db.fileHistoryTbl.setItem(`${vaultRandomID}\t${k2!.key}`, k2), ]); }; diff --git a/src/main.ts b/src/main.ts index 4a22fb3..6ef868e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -133,18 +133,18 @@ const getIconSvg = () => { }; export default class RemotelySavePlugin extends Plugin { - settings: RemotelySavePluginSettings; - db: InternalDBs; - syncStatus: SyncStatusType; - statusBarElement: HTMLSpanElement; - oauth2Info: OAuth2Info; - currLogLevel: string; + settings!: RemotelySavePluginSettings; + db!: InternalDBs; + syncStatus!: SyncStatusType; + statusBarElement!: HTMLSpanElement; + oauth2Info!: OAuth2Info; + currLogLevel!: string; currSyncMsg?: string; syncRibbon?: HTMLElement; autoRunIntervalID?: number; syncOnSaveIntervalID?: number; - i18n: I18n; - vaultRandomID: string; + i18n!: I18n; + vaultRandomID!: string; debugServerTemp?: string; async syncRun(triggerSource: SyncTriggerSourceType = "manual") { @@ -175,7 +175,7 @@ export default class RemotelySavePlugin extends Plugin { let originLabel = `${this.manifest.name}`; if (this.syncRibbon !== undefined) { - originLabel = this.syncRibbon.getAttribute("aria-label"); + originLabel = this.syncRibbon.getAttribute("aria-label") as string; } try { @@ -286,7 +286,8 @@ export default class RemotelySavePlugin extends Plugin { this.db, this.vaultRandomID ); - let localConfigDirContents: ObsConfigDirFileType[] = undefined; + let localConfigDirContents: ObsConfigDirFileType[] | undefined = + undefined; if (this.settings.syncConfigDir) { localConfigDirContents = await listFilesInObsFolder( this.app.vault.configDir, @@ -312,11 +313,11 @@ export default class RemotelySavePlugin extends Plugin { client.serviceType, triggerSource, this.app.vault, - this.settings.syncConfigDir, + this.settings.syncConfigDir ?? false, this.app.vault.configDir, - this.settings.syncUnderscoreItems, - this.settings.skipSizeLargerThan, - this.settings.ignorePaths, + this.settings.syncUnderscoreItems ?? false, + this.settings.skipSizeLargerThan ?? -1, + this.settings.ignorePaths ?? [], this.settings.password ); log.info(plan.mixedStates); // for debugging @@ -350,7 +351,7 @@ export default class RemotelySavePlugin extends Plugin { new SizesConflictModal( self.app, self, - this.settings.skipSizeLargerThan, + this.settings.skipSizeLargerThan ?? -1, ss, this.settings.password !== "" ).open(); @@ -397,7 +398,7 @@ export default class RemotelySavePlugin extends Plugin { this.manifest.id }-${Date.now()}: finish sync, triggerSource=${triggerSource}` ); - } catch (error) { + } catch (error: any) { const msg = t("syncrun_abort", { manifestID: this.manifest.id, theDate: `${Date.now()}`, @@ -412,7 +413,7 @@ export default class RemotelySavePlugin extends Plugin { getNotice(e.message, 10 * 1000); } } else { - getNotice(error.message, 10 * 1000); + getNotice(error?.message ?? "error while sync", 10 * 1000); } this.syncStatus = "idle"; if (this.syncRibbon !== undefined) { @@ -445,7 +446,7 @@ export default class RemotelySavePlugin extends Plugin { await this.checkIfPresetRulesFollowed(); // lang should be load early, but after settings - this.i18n = new I18n(this.settings.lang, async (lang: LangTypeAndAuto) => { + this.i18n = new I18n(this.settings.lang!, async (lang: LangTypeAndAuto) => { this.settings.lang = lang; await this.saveSettings(); }); @@ -475,8 +476,11 @@ export default class RemotelySavePlugin extends Plugin { vaultBasePath, vaultRandomIDFromOldConfigFile ); - } catch (err) { - new Notice(err.message, 10 * 1000); + } catch (err: any) { + new Notice( + err?.message ?? "error of prepareDBAndVaultRandomID", + 10 * 1000 + ); throw err; } @@ -566,14 +570,18 @@ export default class RemotelySavePlugin extends Plugin { this.registerObsidianProtocolHandler( COMMAND_CALLBACK_DROPBOX, async (inputParams) => { - if (inputParams.code !== undefined) { + if ( + inputParams.code !== undefined && + this.oauth2Info?.verifier !== undefined + ) { if (this.oauth2Info.helperModal !== undefined) { - this.oauth2Info.helperModal.contentEl.empty(); + const k = this.oauth2Info.helperModal.contentEl; + k.empty(); t("protocol_dropbox_connecting") .split("\n") .forEach((val) => { - this.oauth2Info.helperModal.contentEl.createEl("p", { + k.createEl("p", { text: val, }); }); @@ -596,7 +604,7 @@ export default class RemotelySavePlugin extends Plugin { const self = this; setConfigBySuccessfullAuthInplaceDropbox( this.settings.dropbox, - authRes, + authRes!, () => self.saveSettings() ); @@ -655,14 +663,18 @@ export default class RemotelySavePlugin extends Plugin { this.registerObsidianProtocolHandler( COMMAND_CALLBACK_ONEDRIVE, async (inputParams) => { - if (inputParams.code !== undefined) { + if ( + inputParams.code !== undefined && + this.oauth2Info?.verifier !== undefined + ) { if (this.oauth2Info.helperModal !== undefined) { - this.oauth2Info.helperModal.contentEl.empty(); + const k = this.oauth2Info.helperModal.contentEl; + k.empty(); t("protocol_onedrive_connecting") .split("\n") .forEach((val) => { - this.oauth2Info.helperModal.contentEl.createEl("p", { + k.createEl("p", { text: val, }); }); @@ -824,7 +836,13 @@ export default class RemotelySavePlugin extends Plugin { this.syncRibbon = undefined; if (this.oauth2Info !== undefined) { this.oauth2Info.helperModal = undefined; - this.oauth2Info = undefined; + this.oauth2Info = { + verifier: "", + helperModal: undefined, + authDiv: undefined, + revokeDiv: undefined, + revokeAuthSetting: undefined, + }; } } @@ -932,7 +950,7 @@ export default class RemotelySavePlugin extends Plugin { let dropboxExpired = false; if ( this.settings.dropbox.refreshToken !== "" && - current >= this.settings.dropbox.credentialsShouldBeDeletedAtTime + current >= this.settings!.dropbox!.credentialsShouldBeDeletedAtTime! ) { dropboxExpired = true; this.settings.dropbox = cloneDeep(DEFAULT_DROPBOX_CONFIG); @@ -942,7 +960,7 @@ export default class RemotelySavePlugin extends Plugin { let onedriveExpired = false; if ( this.settings.onedrive.refreshToken !== "" && - current >= this.settings.onedrive.credentialsShouldBeDeletedAtTime + current >= this.settings!.onedrive!.credentialsShouldBeDeletedAtTime! ) { onedriveExpired = true; this.settings.onedrive = cloneDeep(DEFAULT_ONEDRIVE_CONFIG); @@ -1078,11 +1096,11 @@ export default class RemotelySavePlugin extends Plugin { // ); if ( currentTime - lastModified < - this.settings.syncOnSaveAfterMilliseconds + this.settings!.syncOnSaveAfterMilliseconds! ) { if (!runScheduled) { const scheduleTimeFromNow = - this.settings.syncOnSaveAfterMilliseconds - + this.settings!.syncOnSaveAfterMilliseconds! - (currentTime - lastModified); log.info( `schedule a run for ${scheduleTimeFromNow} milliseconds later` diff --git a/src/metadataOnRemote.ts b/src/metadataOnRemote.ts index 8891547..b1349a4 100644 --- a/src/metadataOnRemote.ts +++ b/src/metadataOnRemote.ts @@ -26,8 +26,8 @@ export interface MetadataOnRemote { } export const isEqualMetadataOnRemote = ( - a: MetadataOnRemote, - b: MetadataOnRemote + a: MetadataOnRemote | undefined, + b: MetadataOnRemote | undefined ) => { const m1 = a === undefined ? { deletions: [] } : a; const m2 = b === undefined ? { deletions: [] } : b; diff --git a/src/misc.ts b/src/misc.ts index a85dbb3..5b33ec4 100644 --- a/src/misc.ts +++ b/src/misc.ts @@ -126,8 +126,12 @@ export const base64ToArrayBuffer = (b64text: string) => { * @returns */ export const hexStringToTypedArray = (hex: string) => { + const f = hex.match(/[\da-f]{2}/gi); + if (f === null) { + throw Error(`input ${hex} is not hex, no way to transform`); + } return new Uint8Array( - hex.match(/[\da-f]{2}/gi).map(function (h) { + f.map(function (h) { return parseInt(h, 16); }) ); @@ -236,7 +240,7 @@ export const setToString = (a: Set, delimiter: string = ",") => { export const extractSvgSub = (x: string, subEl: string = "rect") => { const parser = new window.DOMParser(); const dom = parser.parseFromString(x, "image/svg+xml"); - const svg = dom.querySelector("svg"); + const svg = dom.querySelector("svg")!; svg.setAttribute("viewbox", "0 0 10 10"); return svg.innerHTML; }; @@ -317,7 +321,7 @@ export const getTypeName = (obj: any) => { * @param x * @returns */ -export const atWhichLevel = (x: string) => { +export const atWhichLevel = (x: string | undefined) => { if ( x === undefined || x === "" || @@ -413,11 +417,14 @@ export const toText = (x: any) => { */ export const statFix = async (vault: Vault, path: string) => { const s = await vault.adapter.stat(path); + if (s === undefined || s === null) { + return s; + } if (s.ctime === undefined || s.ctime === null || Number.isNaN(s.ctime)) { - s.ctime = undefined; + s.ctime = undefined as any; // force assignment } if (s.mtime === undefined || s.mtime === null || Number.isNaN(s.mtime)) { - s.mtime = undefined; + s.mtime = undefined as any; // force assignment } if ( (s.size === undefined || s.size === null || Number.isNaN(s.size)) && diff --git a/src/obsFolderLister.ts b/src/obsFolderLister.ts index 668d0ea..d1f9bdf 100644 --- a/src/obsFolderLister.ts +++ b/src/obsFolderLister.ts @@ -53,9 +53,9 @@ export const listFilesInObsFolder = async ( const CHUNK_SIZE = 10; const contents: ObsConfigDirFileType[] = []; while (q.length > 0) { - const itemsToFetch = []; + const itemsToFetch: string[] = []; while (q.length > 0) { - itemsToFetch.push(q.pop()); + itemsToFetch.push(q.pop()!); } const itemsToFetchChunks = chunk(itemsToFetch, CHUNK_SIZE); @@ -63,8 +63,11 @@ export const listFilesInObsFolder = async ( const r = singleChunk.map(async (x) => { const statRes = await statFix(vault, x); + if (statRes === undefined || statRes === null) { + throw Error("something goes wrong while listing hidden folder"); + } const isFolder = statRes.type === "folder"; - let children: ListedFiles = undefined; + let children: ListedFiles | undefined = undefined; if (isFolder) { children = await vault.adapter.list(x); } diff --git a/src/remote.ts b/src/remote.ts index 71b7d6c..6a26fe1 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -43,10 +43,10 @@ export class RemoteClient { "remember to provide vault name and callback while init webdav client" ); } - const remoteBaseDir = webdavConfig.remoteBaseDir || vaultName; + const remoteBaseDir = webdavConfig!.remoteBaseDir || vaultName; this.webdavConfig = webdavConfig; this.webdavClient = webdav.getWebdavClient( - this.webdavConfig, + this.webdavConfig!, remoteBaseDir, saveUpdatedConfigFunc ); @@ -56,10 +56,10 @@ export class RemoteClient { "remember to provide vault name and callback while init dropbox client" ); } - const remoteBaseDir = dropboxConfig.remoteBaseDir || vaultName; + const remoteBaseDir = dropboxConfig!.remoteBaseDir || vaultName; this.dropboxConfig = dropboxConfig; this.dropboxClient = dropbox.getDropboxClient( - this.dropboxConfig, + this.dropboxConfig!, remoteBaseDir, saveUpdatedConfigFunc ); @@ -69,10 +69,10 @@ export class RemoteClient { "remember to provide vault name and callback while init onedrive client" ); } - const remoteBaseDir = onedriveConfig.remoteBaseDir || vaultName; + const remoteBaseDir = onedriveConfig!.remoteBaseDir || vaultName; this.onedriveConfig = onedriveConfig; this.onedriveClient = onedrive.getOnedriveClient( - this.onedriveConfig, + this.onedriveConfig!, remoteBaseDir, saveUpdatedConfigFunc ); @@ -84,17 +84,17 @@ export class RemoteClient { getRemoteMeta = async (fileOrFolderPath: string) => { if (this.serviceType === "s3") { return await s3.getRemoteMeta( - s3.getS3Client(this.s3Config), - this.s3Config, + s3.getS3Client(this.s3Config!), + this.s3Config!, fileOrFolderPath ); } else if (this.serviceType === "webdav") { - return await webdav.getRemoteMeta(this.webdavClient, fileOrFolderPath); + return await webdav.getRemoteMeta(this.webdavClient!, fileOrFolderPath); } else if (this.serviceType === "dropbox") { - return await dropbox.getRemoteMeta(this.dropboxClient, fileOrFolderPath); + return await dropbox.getRemoteMeta(this.dropboxClient!, fileOrFolderPath); } else if (this.serviceType === "onedrive") { return await onedrive.getRemoteMeta( - this.onedriveClient, + this.onedriveClient!, fileOrFolderPath ); } else { @@ -104,7 +104,7 @@ export class RemoteClient { uploadToRemote = async ( fileOrFolderPath: string, - vault: Vault, + vault: Vault | undefined, isRecursively: boolean = false, password: string = "", remoteEncryptedKey: string = "", @@ -114,8 +114,8 @@ export class RemoteClient { ) => { if (this.serviceType === "s3") { return await s3.uploadToRemote( - s3.getS3Client(this.s3Config), - this.s3Config, + s3.getS3Client(this.s3Config!), + this.s3Config!, fileOrFolderPath, vault, isRecursively, @@ -126,7 +126,7 @@ export class RemoteClient { ); } else if (this.serviceType === "webdav") { return await webdav.uploadToRemote( - this.webdavClient, + this.webdavClient!, fileOrFolderPath, vault, isRecursively, @@ -137,7 +137,7 @@ export class RemoteClient { ); } else if (this.serviceType === "dropbox") { return await dropbox.uploadToRemote( - this.dropboxClient, + this.dropboxClient!, fileOrFolderPath, vault, isRecursively, @@ -149,7 +149,7 @@ export class RemoteClient { ); } else if (this.serviceType === "onedrive") { return await onedrive.uploadToRemote( - this.onedriveClient, + this.onedriveClient!, fileOrFolderPath, vault, isRecursively, @@ -167,15 +167,15 @@ export class RemoteClient { listAllFromRemote = async () => { if (this.serviceType === "s3") { return await s3.listAllFromRemote( - s3.getS3Client(this.s3Config), - this.s3Config + s3.getS3Client(this.s3Config!), + this.s3Config! ); } else if (this.serviceType === "webdav") { - return await webdav.listAllFromRemote(this.webdavClient); + return await webdav.listAllFromRemote(this.webdavClient!); } else if (this.serviceType === "dropbox") { - return await dropbox.listAllFromRemote(this.dropboxClient); + return await dropbox.listAllFromRemote(this.dropboxClient!); } else if (this.serviceType === "onedrive") { - return await onedrive.listAllFromRemote(this.onedriveClient); + return await onedrive.listAllFromRemote(this.onedriveClient!); } else { throw Error(`not supported service type ${this.serviceType}`); } @@ -191,8 +191,8 @@ export class RemoteClient { ) => { if (this.serviceType === "s3") { return await s3.downloadFromRemote( - s3.getS3Client(this.s3Config), - this.s3Config, + s3.getS3Client(this.s3Config!), + this.s3Config!, fileOrFolderPath, vault, mtime, @@ -202,7 +202,7 @@ export class RemoteClient { ); } else if (this.serviceType === "webdav") { return await webdav.downloadFromRemote( - this.webdavClient, + this.webdavClient!, fileOrFolderPath, vault, mtime, @@ -212,7 +212,7 @@ export class RemoteClient { ); } else if (this.serviceType === "dropbox") { return await dropbox.downloadFromRemote( - this.dropboxClient, + this.dropboxClient!, fileOrFolderPath, vault, mtime, @@ -222,7 +222,7 @@ export class RemoteClient { ); } else if (this.serviceType === "onedrive") { return await onedrive.downloadFromRemote( - this.onedriveClient, + this.onedriveClient!, fileOrFolderPath, vault, mtime, @@ -242,29 +242,29 @@ export class RemoteClient { ) => { if (this.serviceType === "s3") { return await s3.deleteFromRemote( - s3.getS3Client(this.s3Config), - this.s3Config, + s3.getS3Client(this.s3Config!), + this.s3Config!, fileOrFolderPath, password, remoteEncryptedKey ); } else if (this.serviceType === "webdav") { return await webdav.deleteFromRemote( - this.webdavClient, + this.webdavClient!, fileOrFolderPath, password, remoteEncryptedKey ); } else if (this.serviceType === "dropbox") { return await dropbox.deleteFromRemote( - this.dropboxClient, + this.dropboxClient!, fileOrFolderPath, password, remoteEncryptedKey ); } else if (this.serviceType === "onedrive") { return await onedrive.deleteFromRemote( - this.onedriveClient, + this.onedriveClient!, fileOrFolderPath, password, remoteEncryptedKey @@ -277,17 +277,17 @@ export class RemoteClient { checkConnectivity = async (callbackFunc?: any) => { if (this.serviceType === "s3") { return await s3.checkConnectivity( - s3.getS3Client(this.s3Config), - this.s3Config, + s3.getS3Client(this.s3Config!), + this.s3Config!, callbackFunc ); } else if (this.serviceType === "webdav") { - return await webdav.checkConnectivity(this.webdavClient, callbackFunc); + return await webdav.checkConnectivity(this.webdavClient!, callbackFunc); } else if (this.serviceType === "dropbox") { - return await dropbox.checkConnectivity(this.dropboxClient, callbackFunc); + return await dropbox.checkConnectivity(this.dropboxClient!, callbackFunc); } else if (this.serviceType === "onedrive") { return await onedrive.checkConnectivity( - this.onedriveClient, + this.onedriveClient!, callbackFunc ); } else { @@ -297,9 +297,9 @@ export class RemoteClient { getUser = async () => { if (this.serviceType === "dropbox") { - return await dropbox.getUserDisplayName(this.dropboxClient); + return await dropbox.getUserDisplayName(this.dropboxClient!); } else if (this.serviceType === "onedrive") { - return await onedrive.getUserDisplayName(this.onedriveClient); + return await onedrive.getUserDisplayName(this.onedriveClient!); } else { throw Error(`not supported service type ${this.serviceType}`); } @@ -307,7 +307,7 @@ export class RemoteClient { revokeAuth = async () => { if (this.serviceType === "dropbox") { - return await dropbox.revokeAuth(this.dropboxClient); + return await dropbox.revokeAuth(this.dropboxClient!); } else { throw Error(`not supported service type ${this.serviceType}`); } diff --git a/src/remoteForDropbox.ts b/src/remoteForDropbox.ts index 4a89f6d..c4ee477 100644 --- a/src/remoteForDropbox.ts +++ b/src/remoteForDropbox.ts @@ -24,7 +24,7 @@ import { log } from "./moreOnLog"; export const DEFAULT_DROPBOX_CONFIG: DropboxConfig = { accessToken: "", - clientID: process.env.DEFAULT_DROPBOX_APP_KEY, + clientID: process.env.DEFAULT_DROPBOX_APP_KEY ?? "", refreshToken: "", accessTokenExpiresInSeconds: 0, accessTokenExpiresAtTime: 0, @@ -76,7 +76,7 @@ const fromDropboxItemToRemoteItem = ( | files.DeletedMetadataReference, remoteBaseDir: string ): RemoteItem => { - let key = getNormPath(x.path_display, remoteBaseDir); + let key = getNormPath(x.path_display!, remoteBaseDir); if (x[".tag"] === "folder" && !key.endsWith("/")) { key = `${key}/`; } @@ -101,7 +101,8 @@ const fromDropboxItemToRemoteItem = ( remoteType: "dropbox", etag: `${x.id}\t${x.content_hash}`, } as RemoteItem; - } else if (x[".tag"] === "deleted") { + } else { + // x[".tag"] === "deleted" throw Error("do not support deleted tag"); } }; @@ -188,7 +189,7 @@ export const getAuthUrlAndVerifier = async ( : `obsidian://${COMMAND_CALLBACK_DROPBOX}`; const authUrl = ( await auth.getAuthenticationUrl( - callback, + callback as any, undefined, "code", "offline", @@ -282,9 +283,7 @@ export const setConfigBySuccessfullAuthInplace = async ( if (authRes.refresh_token !== undefined) { config.refreshToken = authRes.refresh_token; - } - if (authRes.refresh_token !== undefined) { - config.accountID = authRes.account_id; + config.accountID = authRes.account_id!; } if (saveUpdatedConfigFunc !== undefined) { @@ -307,7 +306,7 @@ interface ErrSubType { async function retryReq( reqFunc: () => Promise>, extraHint: string = "" -): Promise> { +): Promise | undefined> { const waitSeconds = [1, 2, 4, 8]; // hard code exponential backoff for (let idx = 0; idx < waitSeconds.length; ++idx) { try { @@ -369,7 +368,7 @@ export class WrappedDropboxClient { dropboxConfig: DropboxConfig; remoteBaseDir: string; saveUpdatedConfigFunc: () => Promise; - dropbox: Dropbox; + dropbox!: Dropbox; vaultFolderExists: boolean; constructor( dropboxConfig: DropboxConfig, @@ -507,6 +506,9 @@ export const getRemoteMeta = async ( path: remotePath, }) ); + if (rsp === undefined) { + throw Error("dropbox.filesGetMetadata undefinded"); + } if (rsp.status !== 200) { throw Error(JSON.stringify(rsp)); } @@ -516,7 +518,7 @@ export const getRemoteMeta = async ( export const uploadToRemote = async ( client: WrappedDropboxClient, fileOrFolderPath: string, - vault: Vault, + vault: Vault | undefined, isRecursively: boolean = false, password: string = "", remoteEncryptedKey: string = "", @@ -611,6 +613,11 @@ export const uploadToRemote = async ( localContent = rawContent; } } else { + if (vault === undefined) { + throw new Error( + `the vault variable is not passed but we want to read ${fileOrFolderPath} for Dropbox` + ); + } localContent = await vault.adapter.readBinary(fileOrFolderPath); } let remoteContent = localContent; @@ -700,6 +707,9 @@ const downloadFromRemoteRaw = async ( }), `downloadFromRemoteRaw=${remotePath}` ); + if (rsp === undefined) { + throw Error(`unknown rsp from dropbox download: ${rsp}`); + } if ((rsp.result as any).fileBlob !== undefined) { // we get a Blob const content = (rsp.result as any).fileBlob as Blob; diff --git a/src/remoteForOnedrive.ts b/src/remoteForOnedrive.ts index 1776f8a..9d355bd 100644 --- a/src/remoteForOnedrive.ts +++ b/src/remoteForOnedrive.ts @@ -31,8 +31,8 @@ const REDIRECT_URI = `obsidian://${COMMAND_CALLBACK_ONEDRIVE}`; export const DEFAULT_ONEDRIVE_CONFIG: OnedriveConfig = { accessToken: "", - clientID: process.env.DEFAULT_ONEDRIVE_CLIENT_ID, - authority: process.env.DEFAULT_ONEDRIVE_AUTHORITY, + clientID: process.env.DEFAULT_ONEDRIVE_CLIENT_ID ?? "", + authority: process.env.DEFAULT_ONEDRIVE_AUTHORITY ?? "", refreshToken: "", accessTokenExpiresInSeconds: 0, accessTokenExpiresAtTime: 0, @@ -199,7 +199,7 @@ export const setConfigBySuccessfullAuthInplace = async ( config.accessTokenExpiresAtTime = Date.now() + authRes.expires_in - 5 * 60 * 1000; config.accessTokenExpiresInSeconds = authRes.expires_in; - config.refreshToken = authRes.refresh_token; + config.refreshToken = authRes.refresh_token!; // manually set it expired after 80 days; config.credentialsShouldBeDeletedAtTime = @@ -256,7 +256,9 @@ const getNormPath = (fileOrFolderPath: string, remoteBaseDir: string) => { }; const constructFromDriveItemToRemoteItemError = (x: DriveItem) => { - return `parentPath="${x.parentReference.path}", selfName="${x.name}"`; + return `parentPath="${ + x.parentReference?.path ?? "(no parentReference or path)" + }", selfName="${x.name}"`; }; const fromDriveItemToRemoteItem = ( @@ -281,6 +283,14 @@ const fromDriveItemToRemoteItem = ( // another possibile prefix const FOURTH_COMMON_PREFIX_RAW = `/drive/items/`; + if ( + x.parentReference === undefined || + x.parentReference === null || + x.parentReference.path === undefined || + x.parentReference.path === null + ) { + throw Error("x.parentReference.path is undefinded or null"); + } const fullPathOriginal = `${x.parentReference.path}/${x.name}`; const matchFirstPrefixRes = fullPathOriginal.match(FIRST_COMMON_PREFIX_REGEX); const matchSecondPrefixRes = fullPathOriginal.match( @@ -309,6 +319,11 @@ const fromDriveItemToRemoteItem = ( // it's something like // /drive/items/!:/${remoteBaseDir}/ // with uri encoded! + if (x.name === undefined || x.name === null) { + throw Error( + `OneDrive item no name variable while matching ${FOURTH_COMMON_PREFIX_RAW}` + ); + } const parPath = decodeURIComponent(x.parentReference.path); key = parPath.substring(parPath.indexOf(":") + 1); if (key.startsWith(`/${remoteBaseDir}/`)) { @@ -337,8 +352,8 @@ const fromDriveItemToRemoteItem = ( } return { key: key, - lastModified: Date.parse(x.fileSystemInfo.lastModifiedDateTime), - size: isFolder ? 0 : x.size, + lastModified: Date.parse(x!.fileSystemInfo!.lastModifiedDateTime!), + size: isFolder ? 0 : x.size!, remoteType: "onedrive", etag: x.cTag || "", // do NOT use x.eTag because it changes if meta changes }; @@ -381,7 +396,7 @@ class MyAuthProvider implements AuthenticationProvider { } const r2 = r as AccessCodeResponseSuccessfulType; this.onedriveConfig.accessToken = r2.access_token; - this.onedriveConfig.refreshToken = r2.refresh_token; + this.onedriveConfig.refreshToken = r2.refresh_token!; this.onedriveConfig.accessTokenExpiresInSeconds = r2.expires_in; this.onedriveConfig.accessTokenExpiresAtTime = currentTs + r2.expires_in * 1000 - 60 * 2 * 1000; @@ -680,7 +695,7 @@ export const getRemoteMeta = async ( export const uploadToRemote = async ( client: WrappedOnedriveClient, fileOrFolderPath: string, - vault: Vault, + vault: Vault | undefined, isRecursively: boolean = false, password: string = "", remoteEncryptedKey: string = "", @@ -784,6 +799,11 @@ export const uploadToRemote = async ( localContent = rawContent; } } else { + if (vault === undefined) { + throw new Error( + `the vault variable is not passed but we want to read ${fileOrFolderPath} for OneDrive` + ); + } localContent = await vault.adapter.readBinary(fileOrFolderPath); } let remoteContent = localContent; @@ -842,7 +862,7 @@ export const uploadToRemote = async ( `${uploadFile}:/createUploadSession`, k ); - const uploadUrl = s.uploadUrl; + const uploadUrl = s.uploadUrl!; log.debug("uploadSession = "); log.debug(s); diff --git a/src/remoteForS3.ts b/src/remoteForS3.ts index eacdc32..086d22d 100644 --- a/src/remoteForS3.ts +++ b/src/remoteForS3.ts @@ -55,7 +55,7 @@ import PQueue from "p-queue"; * But this uses Obsidian requestUrl instead. */ class ObsHttpHandler extends FetchHttpHandler { - requestTimeoutInMs: number; + requestTimeoutInMs: number | undefined; constructor(options?: FetchHttpHandlerOptions) { super(options); this.requestTimeoutInMs = @@ -95,7 +95,7 @@ class ObsHttpHandler extends FetchHttpHandler { transformedHeaders[keyLower] = request.headers[key]; } - let contentType: string = undefined; + let contentType: string | undefined = undefined; if (transformedHeaders["content-type"] !== undefined) { contentType = transformedHeaders["content-type"]; } @@ -226,17 +226,17 @@ const fromS3ObjectToRemoteItem = ( mtimeRecords: Record, ctimeRecords: Record ) => { - let mtime = x.LastModified.valueOf(); - if (x.Key in mtimeRecords) { - const m2 = mtimeRecords[x.Key]; + let mtime = x.LastModified!.valueOf(); + if (x.Key! in mtimeRecords) { + const m2 = mtimeRecords[x.Key!]; if (m2 !== 0) { mtime = m2; } } const r: RemoteItem = { - key: getLocalNoPrefixPath(x.Key, remotePrefix), + key: getLocalNoPrefixPath(x.Key!, remotePrefix), lastModified: mtime, - size: x.Size, + size: x.Size!, remoteType: "s3", etag: x.ETag, }; @@ -249,7 +249,7 @@ const fromS3HeadObjectToRemoteItem = ( remotePrefix: string, useAccurateMTime: boolean ) => { - let mtime = x.LastModified.valueOf(); + let mtime = x.LastModified!.valueOf(); if (useAccurateMTime && x.Metadata !== undefined) { const m2 = Math.round( parseFloat(x.Metadata.mtime || x.Metadata.MTime || "0") @@ -317,6 +317,7 @@ export const getRemoteMeta = async ( fileOrFolderPathWithRemotePrefix: string ) => { if ( + s3Config.remotePrefix !== undefined && s3Config.remotePrefix !== "" && !fileOrFolderPathWithRemotePrefix.startsWith(s3Config.remotePrefix) ) { @@ -332,8 +333,8 @@ export const getRemoteMeta = async ( return fromS3HeadObjectToRemoteItem( fileOrFolderPathWithRemotePrefix, res, - s3Config.remotePrefix, - s3Config.useAccurateMTime + s3Config.remotePrefix ?? "", + s3Config.useAccurateMTime ?? false ); }; @@ -341,7 +342,7 @@ export const uploadToRemote = async ( s3Client: S3Client, s3Config: S3Config, fileOrFolderPath: string, - vault: Vault, + vault: Vault | undefined, isRecursively: boolean = false, password: string = "", remoteEncryptedKey: string = "", @@ -354,7 +355,7 @@ export const uploadToRemote = async ( if (password !== "") { uploadFile = remoteEncryptedKey; } - uploadFile = getRemoteWithPrefixPath(uploadFile, s3Config.remotePrefix); + uploadFile = getRemoteWithPrefixPath(uploadFile, s3Config.remotePrefix ?? ""); const isFolder = fileOrFolderPath.endsWith("/"); if (isFolder && isRecursively) { @@ -407,8 +408,13 @@ export const uploadToRemote = async ( mtime = rawContentMTime; ctime = rawContentCTime; } else { + if (vault === undefined) { + throw new Error( + `the vault variable is not passed but we want to read ${fileOrFolderPath} for S3` + ); + } localContent = await vault.adapter.readBinary(fileOrFolderPath); - const s = await vault?.adapter?.stat(fileOrFolderPath); + const s = await vault.adapter.stat(fileOrFolderPath); if (s !== undefined && s !== null) { mtime = s.mtime; ctime = s.ctime; @@ -500,12 +506,12 @@ const listFromRemoteRaw = async ( if (rspHead.Metadata === undefined) { // pass } else { - mtimeRecords[content.Key] = Math.round( + mtimeRecords[content.Key!] = Math.round( parseFloat( rspHead.Metadata.mtime || rspHead.Metadata.MTime || "0" ) ); - ctimeRecords[content.Key] = Math.round( + ctimeRecords[content.Key!] = Math.round( parseFloat( rspHead.Metadata.ctime || rspHead.Metadata.CTime || "0" ) @@ -515,7 +521,7 @@ const listFromRemoteRaw = async ( } } - isTruncated = rsp.IsTruncated; + isTruncated = rsp.IsTruncated ?? false; confCmd.ContinuationToken = rsp.NextContinuationToken; if ( isTruncated && @@ -536,7 +542,7 @@ const listFromRemoteRaw = async ( Contents: contents.map((x) => fromS3ObjectToRemoteItem( x, - s3Config.remotePrefix, + s3Config.remotePrefix ?? "", mtimeRecords, ctimeRecords ) @@ -559,8 +565,11 @@ export const listAllFromRemote = async ( * @returns Promise */ const getObjectBodyToArrayBuffer = async ( - b: Readable | ReadableStream | Blob + b: Readable | ReadableStream | Blob | undefined ) => { + if (b === undefined) { + throw Error(`ObjectBody is undefined and don't know how to deal with it`); + } if (b instanceof Readable) { return (await new Promise((resolve, reject) => { const chunks: Uint8Array[] = []; @@ -583,6 +592,7 @@ const downloadFromRemoteRaw = async ( fileOrFolderPathWithRemotePrefix: string ) => { if ( + s3Config.remotePrefix !== undefined && s3Config.remotePrefix !== "" && !fileOrFolderPathWithRemotePrefix.startsWith(s3Config.remotePrefix) ) { @@ -626,7 +636,10 @@ export const downloadFromRemote = async ( if (password !== "") { downloadFile = remoteEncryptedKey; } - downloadFile = getRemoteWithPrefixPath(downloadFile, s3Config.remotePrefix); + downloadFile = getRemoteWithPrefixPath( + downloadFile, + s3Config.remotePrefix ?? "" + ); const remoteContent = await downloadFromRemoteRaw( s3Client, s3Config, @@ -668,7 +681,7 @@ export const deleteFromRemote = async ( } remoteFileName = getRemoteWithPrefixPath( remoteFileName, - s3Config.remotePrefix + s3Config.remotePrefix ?? "" ); await s3Client.send( new DeleteObjectCommand({ @@ -734,7 +747,7 @@ export const checkConnectivity = async ( return false; } return results.$metadata.httpStatusCode === 200; - } catch (err) { + } catch (err: any) { log.debug(err); if (callbackFunc !== undefined) { if (s3Config.s3Endpoint.contains(s3Config.s3BucketName)) { diff --git a/src/remoteForWebdav.ts b/src/remoteForWebdav.ts index 3e1be07..79eaed5 100644 --- a/src/remoteForWebdav.ts +++ b/src/remoteForWebdav.ts @@ -39,12 +39,16 @@ if (VALID_REQURL) { }); let contentType: string | undefined = - r.headers["Content-Type"] || - r.headers["content-type"] || - options.headers["Content-Type"] || - options.headers["content-type"] || - options.headers["Accept"] || - options.headers["accept"]; + r.headers["Content-Type"] || r.headers["content-type"]; + if (options.headers !== undefined) { + contentType = + contentType || + options.headers["Content-Type"] || + options.headers["content-type"] || + options.headers["Accept"] || + options.headers["accept"]; + } + if (contentType !== undefined) { contentType = contentType.toLowerCase(); } @@ -106,7 +110,7 @@ if (VALID_REQURL) { // ); // } - let r2: Response = undefined; + let r2: Response | undefined = undefined; if ([101, 103, 204, 205, 304].includes(r.status)) { // A null body status is a status that is 101, 103, 204, 205, or 304. // https://fetch.spec.whatwg.org/#statuses @@ -193,7 +197,7 @@ const fromWebdavItemToRemoteItem = (x: FileStat, remoteBaseDir: string) => { export class WrappedWebdavClient { webdavConfig: WebdavConfig; remoteBaseDir: string; - client: WebDAVClient; + client!: WebDAVClient; vaultFolderExists: boolean; saveUpdatedConfigFunc: () => Promise; constructor( @@ -340,7 +344,7 @@ export const getRemoteMeta = async ( export const uploadToRemote = async ( client: WrappedWebdavClient, fileOrFolderPath: string, - vault: Vault, + vault: Vault | undefined, isRecursively: boolean = false, password: string = "", remoteEncryptedKey: string = "", @@ -392,6 +396,11 @@ export const uploadToRemote = async ( localContent = rawContent; } } else { + if (vault == undefined) { + throw new Error( + `the vault variable is not passed but we want to read ${fileOrFolderPath} for webdav` + ); + } localContent = await vault.adapter.readBinary(fileOrFolderPath); } let remoteContent = localContent; @@ -428,9 +437,9 @@ export const listAllFromRemote = async (client: WrappedWebdavClient) => { const q = new Queue([`/${client.remoteBaseDir}`]); const CHUNK_SIZE = 10; while (q.length > 0) { - const itemsToFetch = []; + const itemsToFetch: string[] = []; while (q.length > 0) { - itemsToFetch.push(q.pop()); + itemsToFetch.push(q.pop()!); } const itemsToFetchChunks = chunk(itemsToFetch, CHUNK_SIZE); // log.debug(itemsToFetchChunks); diff --git a/src/settings.ts b/src/settings.ts index 1213501..153ed74 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -416,7 +416,7 @@ class DropboxAuthModal extends Modal { const self = this; setConfigBySuccessfullAuthInplace( this.plugin.settings.dropbox, - authRes, + authRes!, () => self.plugin.saveSettings() ); const client = new RemoteClient( @@ -784,7 +784,7 @@ const getEyesElements = () => { const wrapTextWithPasswordHide = (text: TextComponent) => { const { eye, eyeOff } = getEyesElements(); - const hider = text.inputEl.insertAdjacentElement("afterend", createSpan()); + const hider = text.inputEl.insertAdjacentElement("afterend", createSpan())!; // the init type of hider is "hidden" === eyeOff === password hider.innerHTML = eyeOff; hider.addEventListener("click", (e) => { @@ -1534,8 +1534,8 @@ export class RemotelySaveSettingTab extends PluginSettingTab { dropdown .setValue(this.plugin.settings.webdav.authType) - .onChange(async (val: WebdavAuthType) => { - this.plugin.settings.webdav.authType = val; + .onChange(async (val) => { + this.plugin.settings.webdav.authType = val as WebdavAuthType; await this.plugin.saveSettings(); }); }); @@ -1548,20 +1548,20 @@ export class RemotelySaveSettingTab extends PluginSettingTab { dropdown.addOption("manual_1", t("settings_webdav_depth_1")); dropdown.addOption("manual_infinity", t("settings_webdav_depth_inf")); - let initVal = "auto"; + let initVal: WebdavDepthType = "auto"; const autoOptions: Set = new Set([ "auto_unknown", "auto_1", "auto_infinity", ]); - if (autoOptions.has(this.plugin.settings.webdav.depth)) { + if (autoOptions.has(this.plugin.settings.webdav.depth as any)) { initVal = "auto"; } else { initVal = this.plugin.settings.webdav.depth || "auto"; } type DepthOption = "auto" | "manual_1" | "manual_infinity"; - dropdown.setValue(initVal).onChange(async (val: DepthOption) => { + dropdown.setValue(initVal).onChange(async (val) => { if (val === "auto") { this.plugin.settings.webdav.depth = "auto_unknown"; this.plugin.settings.webdav.manualRecursive = false; @@ -1656,8 +1656,8 @@ export class RemotelySaveSettingTab extends PluginSettingTab { dropdown.addOption("onedrive", t("settings_chooseservice_onedrive")); dropdown .setValue(this.plugin.settings.serviceType) - .onChange(async (val: SUPPORTED_SERVICES_TYPE) => { - this.plugin.settings.serviceType = val; + .onChange(async (val) => { + this.plugin.settings.serviceType = val as SUPPORTED_SERVICES_TYPE; s3Div.toggleClass( "s3-hide", this.plugin.settings.serviceType !== "s3" @@ -1815,11 +1815,11 @@ export class RemotelySaveSettingTab extends PluginSettingTab { // ); if ( currentTime - lastModified < - this.plugin.settings.syncOnSaveAfterMilliseconds + this.plugin.settings.syncOnSaveAfterMilliseconds! ) { if (!runScheduled) { const scheduleTimeFromNow = - this.plugin.settings.syncOnSaveAfterMilliseconds - + this.plugin.settings.syncOnSaveAfterMilliseconds! - (currentTime - lastModified); log.info( `schedule a run for ${scheduleTimeFromNow} milliseconds later` @@ -1864,7 +1864,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab { .setDesc(t("settings_enablestatusbar_info_desc")) .addToggle((toggle) => { toggle - .setValue(this.plugin.settings.enableStatusBarInfo) + .setValue(this.plugin.settings.enableStatusBarInfo ?? false) .onChange(async (val) => { this.plugin.settings.enableStatusBarInfo = val; await this.plugin.saveSettings(); @@ -1897,7 +1897,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab { .addTextArea((textArea) => { textArea - .setValue(`${this.plugin.settings.ignorePaths.join("\n")}`) + .setValue(`${(this.plugin.settings.ignorePaths ?? []).join("\n")}`) .onChange(async (value) => { this.plugin.settings.ignorePaths = value .trim() @@ -1999,9 +1999,9 @@ export class RemotelySaveSettingTab extends PluginSettingTab { t("settings_deletetowhere_obsidian_trash") ); dropdown - .setValue(this.plugin.settings.deleteToWhere) - .onChange(async (val: "system" | "obsidian") => { - this.plugin.settings.deleteToWhere = val; + .setValue(this.plugin.settings.deleteToWhere ?? "system") + .onChange(async (val) => { + this.plugin.settings.deleteToWhere = val as "system" | "obsidian"; await this.plugin.saveSettings(); }); }); @@ -2044,7 +2044,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab { dropdown.addOption("info", "info"); dropdown.addOption("debug", "debug"); dropdown - .setValue(this.plugin.settings.currLogLevel) + .setValue(this.plugin.settings.currLogLevel ?? "info") .onChange(async (val: string) => { this.plugin.settings.currLogLevel = val; log.setLevel(val as any); diff --git a/src/sync.ts b/src/sync.ts index 7c4af79..1180ec1 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -186,7 +186,7 @@ export const parseRemoteItems = async ( password: string = "" ) => { const remoteStates = [] as FileOrFolderMixedState[]; - let metadataFile: FileOrFolderMixedState = undefined; + let metadataFile: FileOrFolderMixedState | undefined = undefined; if (remote === undefined) { return { remoteStates: remoteStates, @@ -212,8 +212,8 @@ export const parseRemoteItems = async ( remoteType, db, key, - entry.lastModified, - entry.etag, + entry.lastModified ?? Date.now(), + entry.etag ?? "", vaultRandomID ); @@ -268,7 +268,7 @@ export const parseRemoteItems = async ( }; export const fetchMetadataFile = async ( - metadataFile: FileOrFolderMixedState, + metadataFile: FileOrFolderMixedState | undefined, client: RemoteClient, vault: Vault, password: string = "" @@ -283,7 +283,7 @@ export const fetchMetadataFile = async ( const buf = await client.downloadFromRemote( metadataFile.key, vault, - metadataFile.mtimeRemote, + metadataFile.mtimeRemote ?? Date.now(), password, metadataFile.remoteEncryptedKey, true @@ -325,8 +325,8 @@ const ensembleMixedStates = async ( remoteStates: FileOrFolderMixedState[], local: TAbstractFile[], localConfigDirContents: ObsConfigDirFileType[] | undefined, - remoteDeleteHistory: DeletionOnRemote[], - localFileHistory: FileFolderHistoryRecord[], + remoteDeleteHistory: DeletionOnRemote[] | undefined, + localFileHistory: FileFolderHistoryRecord[] | undefined, syncConfigDir: boolean, configDir: string, syncUnderscoreItems: boolean, @@ -413,7 +413,10 @@ const ensembleMixedStates = async ( if (syncConfigDir && localConfigDirContents !== undefined) { for (const entry of localConfigDirContents) { const key = entry.key; - let mtimeLocal = Math.max(entry.mtime ?? 0, entry.ctime ?? 0); + let mtimeLocal: number | undefined = Math.max( + entry.mtime ?? 0, + entry.ctime ?? 0 + ); if (Number.isNaN(mtimeLocal) || mtimeLocal === 0) { mtimeLocal = undefined; } @@ -453,7 +456,7 @@ const ensembleMixedStates = async ( } } - for (const entry of remoteDeleteHistory) { + for (const entry of remoteDeleteHistory ?? []) { const key = entry.key; const r = { key: key, @@ -485,7 +488,7 @@ const ensembleMixedStates = async ( } } - for (const entry of localFileHistory) { + for (const entry of localFileHistory ?? []) { let key = entry.key; if (entry.keyType === "folder") { if (!entry.key.endsWith("/")) { @@ -532,7 +535,7 @@ const ensembleMixedStates = async ( changeLocalMtimeUsingMapping: true, }; if (results.hasOwnProperty(key)) { - let mtimeLocal = Math.max( + let mtimeLocal: number | undefined = Math.max( r.mtimeLocal ?? 0, results[key].mtimeLocal ?? 0 ); @@ -624,13 +627,13 @@ const assignOperationToFileInplace = ( // 1. mtimeLocal if (r.existLocal) { - const mtimeRemote = r.existRemote ? r.mtimeRemote : -1; + const mtimeRemote = r.existRemote ? r.mtimeRemote! : -1; const deltimeRemote = r.deltimeRemote !== undefined ? r.deltimeRemote : -1; const deltimeLocal = r.deltimeLocal !== undefined ? r.deltimeLocal : -1; if ( - r.mtimeLocal >= mtimeRemote && - r.mtimeLocal >= deltimeLocal && - r.mtimeLocal >= deltimeRemote + r.mtimeLocal! >= mtimeRemote && + r.mtimeLocal! >= deltimeLocal && + r.mtimeLocal! >= deltimeRemote ) { if (sizeLocalComp === undefined) { throw new Error( @@ -654,7 +657,7 @@ const assignOperationToFileInplace = ( } else { // limit the sizes if (sizeLocalComp <= skipSizeLargerThan) { - if (sizeRemoteComp <= skipSizeLargerThan) { + if (sizeRemoteComp! <= skipSizeLargerThan) { r.decision = "uploadLocalToRemote"; r.decisionBranch = 18; } else { @@ -662,7 +665,7 @@ const assignOperationToFileInplace = ( r.decisionBranch = 19; } } else { - if (sizeRemoteComp <= skipSizeLargerThan) { + if (sizeRemoteComp! <= skipSizeLargerThan) { r.decision = "errorLocalTooLargeConflictRemote"; r.decisionBranch = 20; } else { @@ -713,13 +716,13 @@ const assignOperationToFileInplace = ( // 2. mtimeRemote if (r.existRemote) { - const mtimeLocal = r.existLocal ? r.mtimeLocal : -1; + const mtimeLocal = r.existLocal ? r.mtimeLocal! : -1; const deltimeRemote = r.deltimeRemote !== undefined ? r.deltimeRemote : -1; const deltimeLocal = r.deltimeLocal !== undefined ? r.deltimeLocal : -1; if ( - r.mtimeRemote > mtimeLocal && - r.mtimeRemote >= deltimeLocal && - r.mtimeRemote >= deltimeRemote + r.mtimeRemote! > mtimeLocal && + r.mtimeRemote! >= deltimeLocal && + r.mtimeRemote! >= deltimeRemote ) { // we have remote laregest mtime, // and the local not existing or smaller mtime @@ -771,8 +774,8 @@ const assignOperationToFileInplace = ( // 3. deltimeLocal if (r.deltimeLocal !== undefined && r.deltimeLocal !== 0) { - const mtimeLocal = r.existLocal ? r.mtimeLocal : -1; - const mtimeRemote = r.existRemote ? r.mtimeRemote : -1; + const mtimeLocal = r.existLocal ? r.mtimeLocal! : -1; + const mtimeRemote = r.existRemote ? r.mtimeRemote! : -1; const deltimeRemote = r.deltimeRemote !== undefined ? r.deltimeRemote : -1; if ( r.deltimeLocal >= mtimeLocal && @@ -787,9 +790,9 @@ const assignOperationToFileInplace = ( } } else { const localTooLargeToDelete = - r.existLocal && sizeLocalComp > skipSizeLargerThan; + r.existLocal && sizeLocalComp! > skipSizeLargerThan; const remoteTooLargeToDelete = - r.existRemote && sizeRemoteComp > skipSizeLargerThan; + r.existRemote && sizeRemoteComp! > skipSizeLargerThan; if (localTooLargeToDelete) { if (remoteTooLargeToDelete) { r.decision = "skipUsingLocalDelTooLarge"; @@ -824,8 +827,8 @@ const assignOperationToFileInplace = ( // 4. deltimeRemote if (r.deltimeRemote !== undefined && r.deltimeRemote !== 0) { - const mtimeLocal = r.existLocal ? r.mtimeLocal : -1; - const mtimeRemote = r.existRemote ? r.mtimeRemote : -1; + const mtimeLocal = r.existLocal ? r.mtimeLocal! : -1; + const mtimeRemote = r.existRemote ? r.mtimeRemote! : -1; const deltimeLocal = r.deltimeLocal !== undefined ? r.deltimeLocal : -1; if ( r.deltimeRemote >= mtimeLocal && @@ -840,9 +843,9 @@ const assignOperationToFileInplace = ( } } else { const localTooLargeToDelete = - r.existLocal && sizeLocalComp > skipSizeLargerThan; + r.existLocal && sizeLocalComp! > skipSizeLargerThan; const remoteTooLargeToDelete = - r.existRemote && sizeRemoteComp > skipSizeLargerThan; + r.existRemote && sizeRemoteComp! > skipSizeLargerThan; if (localTooLargeToDelete) { if (remoteTooLargeToDelete) { r.decision = "skipUsingRemoteDelTooLarge"; @@ -905,7 +908,13 @@ const assignOperationToFolderInplace = async ( // if it was created after deletion, we should keep it as is if (requireApiVersion(API_VER_STAT_FOLDER)) { if (r.existLocal) { - const { ctime, mtime } = await statFix(vault, r.key); + let ctime = 0; + let mtime = 0; + const s = await statFix(vault, r.key); + if (s !== undefined && s !== null) { + ctime = s.ctime; + mtime = s.mtime; + } const cmtime = Math.max(ctime ?? 0, mtime ?? 0); if ( !Number.isNaN(cmtime) && @@ -936,9 +945,9 @@ const assignOperationToFolderInplace = async ( if ( r.existLocal && r.changeLocalMtimeUsingMapping && - r.mtimeLocal > 0 && - r.mtimeLocal > deltimeLocal && - r.mtimeLocal > deltimeRemote + r.mtimeLocal! > 0 && + r.mtimeLocal! > deltimeLocal && + r.mtimeLocal! > deltimeRemote ) { keptFolder.add(getParentFolder(r.key)); if (r.existLocal && r.existRemote) { @@ -1018,8 +1027,8 @@ export const getSyncPlan = async ( remoteStates: FileOrFolderMixedState[], local: TAbstractFile[], localConfigDirContents: ObsConfigDirFileType[] | undefined, - remoteDeleteHistory: DeletionOnRemote[], - localFileHistory: FileFolderHistoryRecord[], + remoteDeleteHistory: DeletionOnRemote[] | undefined, + localFileHistory: FileFolderHistoryRecord[] | undefined, remoteType: SUPPORTED_SERVICES_TYPE, triggerSource: SyncTriggerSourceType, vault: Vault, @@ -1071,30 +1080,30 @@ export const getSyncPlan = async ( ); } - if (SIZES_GO_WRONG_DECISIONS.has(val.decision)) { + if (SIZES_GO_WRONG_DECISIONS.has(val.decision!)) { sizesGoWrong.push(val); } - if (DELETION_DECISIONS.has(val.decision)) { + if (DELETION_DECISIONS.has(val.decision!)) { if (val.decision === "uploadLocalDelHistToRemote") { deletions.push({ key: key, - actionWhen: val.deltimeLocal, + actionWhen: val.deltimeLocal!, }); } else if (val.decision === "keepRemoteDelHist") { deletions.push({ key: key, - actionWhen: val.deltimeRemote, + actionWhen: val.deltimeRemote!, }); } else if (val.decision === "uploadLocalDelHistToRemoteFolder") { deletions.push({ key: key, - actionWhen: val.deltimeLocal, + actionWhen: val.deltimeLocal!, }); } else if (val.decision === "keepRemoteDelHistFolder") { deletions.push({ key: key, - actionWhen: val.deltimeRemote, + actionWhen: val.deltimeRemote!, }); } else { throw Error(`do not know how to delete for decision ${val.decision}`); @@ -1131,7 +1140,7 @@ const uploadExtraMeta = async ( } const key = DEFAULT_FILE_NAME_FOR_METADATAONREMOTE; - let remoteEncryptedKey = key; + let remoteEncryptedKey: string | undefined = key; if (password !== "") { if (metadataFile === undefined) { @@ -1180,7 +1189,7 @@ const dispatchOperationToActual = async ( localDeleteFunc: any, password: string = "" ) => { - let remoteEncryptedKey = key; + let remoteEncryptedKey: string | undefined = key; if (password !== "") { remoteEncryptedKey = r.remoteEncryptedKey; if (remoteEncryptedKey === undefined || remoteEncryptedKey === "") { @@ -1233,12 +1242,12 @@ const dispatchOperationToActual = async ( client.serviceType, db, r.key, - r.mtimeLocal, - r.sizeLocal, + r.mtimeLocal!, + r.sizeLocal!, r.key, - remoteObjMeta.lastModified, + remoteObjMeta.lastModified ?? Date.now(), remoteObjMeta.size, - remoteObjMeta.etag, + remoteObjMeta.etag ?? "", vaultRandomID ); } @@ -1248,7 +1257,7 @@ const dispatchOperationToActual = async ( await client.downloadFromRemote( r.key, vault, - r.mtimeRemote, + r.mtimeRemote!, password, remoteEncryptedKey ); @@ -1269,12 +1278,12 @@ const dispatchOperationToActual = async ( client.serviceType, db, r.key, - r.mtimeLocal, - r.sizeLocal, + r.mtimeLocal!, + r.sizeLocal!, r.key, - remoteObjMeta.lastModified, + remoteObjMeta.lastModified ?? Date.now(), remoteObjMeta.size, - remoteObjMeta.etag, + remoteObjMeta.etag ?? "", vaultRandomID ); } @@ -1388,7 +1397,7 @@ export const doActualSync = async ( vault: Vault, syncPlan: SyncPlanType, sortedKeys: string[], - metadataFile: FileOrFolderMixedState, + metadataFile: FileOrFolderMixedState | undefined, origMetadata: MetadataOnRemote, sizesGoWrong: FileOrFolderMixedState[], deletions: DeletionOnRemote[], diff --git a/tests/metadataOnRemote.test.ts b/tests/metadataOnRemote.test.ts index 35eb5fe..a72be89 100644 --- a/tests/metadataOnRemote.test.ts +++ b/tests/metadataOnRemote.test.ts @@ -45,8 +45,8 @@ describe("Metadata operations tests", () => { }); it("should treat undefined correctly", async () => { - const a: MetadataOnRemote = undefined; - let b: MetadataOnRemote = { + const a: MetadataOnRemote | undefined = undefined; + let b: MetadataOnRemote | undefined = { deletions: [ { key: "xxx", actionWhen: 1 }, { key: "yyy", actionWhen: 2 }, diff --git a/tsconfig.json b/tsconfig.json index b015b26..a53f4bd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "inlineSources": true, "module": "ESNext", "target": "es6", + "strict": true, "allowJs": true, "noImplicitAny": true, "moduleResolution": "node",