import { Buffer } from "buffer" ; import { Vault } from "obsidian" ; import type { FileStat , WebDAVClient } from "webdav/web" ; import { AuthType , BufferLike , createClient } from "webdav/web" ; import type { RemoteItem , WebdavConfig } from "./baseTypes" ; import { decryptArrayBuffer , encryptArrayBuffer } from "./encrypt" ; import { bufferToArrayBuffer , getPathFolder , mkdirpInVault } from "./misc" ; export type { WebDAVClient } from "webdav/web" ; import * as origLog from "loglevel" ; const log = origLog . getLogger ( "rs-default" ) ; export const DEFAULT_WEBDAV_CONFIG = { address : "" , username : "" , password : "" , authType : "basic" , } as WebdavConfig ; const getWebdavPath = ( fileOrFolderPath , vaultName ) => { let key = fileOrFolderPath ; if ( fileOrFolderPath === "/" || fileOrFolderPath === "" ) { key = `/${ vaultName }/` ; } if ( ! fileOrFolderPath . startsWith ( "/" ) ) { key = `/${ vaultName }/${ fileOrFolderPath }` ; } return key ; } ; const getNormPath = ( fileOrFolderPath , vaultName ) => { if ( ! ( fileOrFolderPath === `/${ vaultName }` || fileOrFolderPath . startsWith ( `/${ vaultName }/` ) ) ) { throw Error ( `"${ fileOrFolderPath }" doesn't starts with "/${ vaultName }/"` ) ; } return fileOrFolderPath . slice ( `/${ vaultName }/` . length ) ; } ; const fromWebdavItemToRemoteItem = ( x , vaultName ) => { let key = getNormPath ( x . filename , vaultName ) ; if ( x . type === "directory" && ! key . endsWith ( "/" ) ) { key = `${ key }/` ; } return { key : key , lastModified : Date . parse ( x . lastmod ) . valueOf ( ) , size : x . size , remoteType : "webdav" , etag : x . etag || undefined , } as RemoteItem ; } ; export class WrappedWebdavClient { webdavConfig : WebdavConfig ; vaultName : string ; client : WebDAVClient ; vaultFolderExists : boolean ; constructor ( webdavConfig , vaultName ) { this . webdavConfig = webdavConfig ; this . vaultName = vaultName ; this . vaultFolderExists = false ; } init = async ( ) => { if ( this . client === undefined ) { if ( this . webdavConfig . username !== "" && this . webdavConfig . password !== "" ) { this . client = createClient ( this . webdavConfig . address , { username : this . webdavConfig . username , password : this . webdavConfig . password , authType : this . webdavConfig . authType === "digest" ? AuthType . Digest : AuthType . Password , } ) ; } else { log . info ( "no password" ) ; this . client = createClient ( this . webdavConfig . address ) ; } } if ( this . vaultFolderExists ) { } else { const res = await this . client . exists ( `/${ this . vaultName }` ) ; if ( res ) { this . vaultFolderExists = true ; } else { log . info ( "remote vault folder not exists, creating" ) ; await this . client . createDirectory ( `/${ this . vaultName }` ) ; log . info ( "remote vault folder created!" ) ; this . vaultFolderExists = true ; } } } ; } export const getWebdavClient = ( webdavConfig , vaultName ) => { return new WrappedWebdavClient ( webdavConfig , vaultName ) ; } ; export const getRemoteMeta = async ( client , fileOrFolderPath ) => { await client . init ( ) ; const remotePath = getWebdavPath ( fileOrFolderPath , client . vaultName ) ; const res = ( await client . client . stat ( remotePath , { details : false , } ) ) as FileStat ; return fromWebdavItemToRemoteItem ( res , client . vaultName ) ; } ; export const uploadToRemote = async ( client , fileOrFolderPath , vault , isRecursively = false , password = "" , remoteEncryptedKey = "" ) => { await client . init ( ) ; let uploadFile = fileOrFolderPath ; if ( password !== "" ) { uploadFile = remoteEncryptedKey ; } uploadFile = getWebdavPath ( uploadFile , client . vaultName ) ; const isFolder = fileOrFolderPath . endsWith ( "/" ) ; if ( isFolder && isRecursively ) { throw Error ( "upload function doesn't implement recursive function yet!" ) ; } else if ( isFolder && ! isRecursively ) { if ( password === "" ) { await client . client . createDirectory ( uploadFile , { recursive : true , } ) ; const res = await getRemoteMeta ( client , uploadFile ) ; return res ; } else { await client . client . putFileContents ( uploadFile , "" , { overwrite : true , onUploadProgress : ( progress ) => { } , } ) ; return await getRemoteMeta ( client , uploadFile ) ; } } else { const localContent = await vault . adapter . readBinary ( fileOrFolderPath ) ; let remoteContent = localContent ; if ( password !== "" ) { remoteContent = await encryptArrayBuffer ( localContent , password ) ; } const dir = getPathFolder ( uploadFile ) ; if ( dir !== "/" && dir !== "" ) { await client . client . createDirectory ( dir , { recursive : true } ) ; } await client . client . putFileContents ( uploadFile , remoteContent , { overwrite : true , onUploadProgress : ( progress ) => { log . info ( `Uploaded ${ progress . loaded } bytes of ${ progress . total }` ) ; } , } ) ; return await getRemoteMeta ( client , uploadFile ) ; } } ; export const listFromRemote = async ( client , prefix ? ) => { if ( prefix !== undefined ) { throw Error ( "prefix not supported" ) ; } await client . init ( ) ; const contents = ( await client . client . getDirectoryContents ( `/${ client . vaultName }` , { deep : true , details : false , glob : "/**" , } ) ) as FileStat [ ] ; return { Contents : contents . map ( ( x ) => fromWebdavItemToRemoteItem ( x , client . vaultName ) ) , } ; } ; const downloadFromRemoteRaw = async ( client , fileOrFolderPath ) => { await client . init ( ) ; const buff = ( await client . client . getFileContents ( getWebdavPath ( fileOrFolderPath , client . vaultName ) ) ) as BufferLike ; if ( buff instanceof ArrayBuffer ) { return buff ; } else if ( buff instanceof Buffer ) { return bufferToArrayBuffer ( buff ) ; } throw Error ( `unexpected file content result with type ${ typeof buff }` ) ; } ; export const downloadFromRemote = async ( client , fileOrFolderPath , vault , mtime , password = "" , remoteEncryptedKey = "" ) => { await client . init ( ) ; const isFolder = fileOrFolderPath . endsWith ( "/" ) ; await mkdirpInVault ( fileOrFolderPath , vault ) ; if ( isFolder ) { } else { let downloadFile = fileOrFolderPath ; if ( password !== "" ) { downloadFile = remoteEncryptedKey ; } downloadFile = getWebdavPath ( downloadFile , client . vaultName ) ; const remoteContent = await downloadFromRemoteRaw ( client , downloadFile ) ; let localContent = remoteContent ; if ( password !== "" ) { localContent = await decryptArrayBuffer ( remoteContent , password ) ; } await vault . adapter . writeBinary ( fileOrFolderPath , localContent , { mtime : mtime , } ) ; } } ; export const deleteFromRemote = async ( client , fileOrFolderPath , password = "" , remoteEncryptedKey = "" ) => { if ( fileOrFolderPath === "/" ) { return ; } let remoteFileName = fileOrFolderPath ; if ( password !== "" ) { remoteFileName = remoteEncryptedKey ; } remoteFileName = getWebdavPath ( remoteFileName , client . vaultName ) ; await client . init ( ) ; try { await client . client . deleteFile ( remoteFileName ) ; } catch ( err ) { console . error ( "some error while deleting" ) ; log . info ( err ) ; } } ; export const checkConnectivity = async ( client ) => { try { await client . init ( ) ; const results = await getRemoteMeta ( client , "/" ) ; if ( results === undefined ) { return false ; } return true ; } catch ( err ) { return false ; } } ;