diff --git a/app/src/main/kotlin/com/syncflow/data/providers/sftp/SftpProvider.kt b/app/src/main/kotlin/com/syncflow/data/providers/sftp/SftpProvider.kt index 853569e..ef0c570 100644 --- a/app/src/main/kotlin/com/syncflow/data/providers/sftp/SftpProvider.kt +++ b/app/src/main/kotlin/com/syncflow/data/providers/sftp/SftpProvider.kt @@ -23,19 +23,36 @@ class SftpProvider(private val account: CloudAccount, private val credentialStor private val password = creds["password"]?.jsonPrimitive?.content private val privateKey = creds["private_key"]?.jsonPrimitive?.content - private fun withSftp(block: (SFTPClient) -> T): T { + // Persistent SSH connection reused across all operations in the provider's lifetime. + // Each call to withSftp checks liveness and reconnects if the connection dropped. + // This eliminates the per-operation connect/auth/disconnect cycle that caused + // 100+ SSH handshakes during a recursive directory listing + file-transfer sync, + // leading to connection timeouts on large folder trees (e.g. 69 subdirectories). + private var sshClient: SSHClient? = null + + private fun getOrCreateSsh(): SSHClient { + val existing = sshClient + if (existing != null && existing.isConnected && existing.isAuthenticated) return existing val ssh = SSHClient() ssh.addHostKeyVerifier(TofuHostKeyVerifier(credentialStore)) ssh.connect(host, port) - try { - if (!privateKey.isNullOrBlank()) { - ssh.authPublickey(username, ssh.loadKeys(privateKey, null, null)) - } else { - ssh.authPassword(username, password ?: "") - } - return ssh.newSFTPClient().use(block) - } finally { - ssh.disconnect() + if (!privateKey.isNullOrBlank()) { + ssh.authPublickey(username, ssh.loadKeys(privateKey, null, null)) + } else { + ssh.authPassword(username, password ?: "") + } + sshClient = ssh + return ssh + } + + private fun withSftp(block: (SFTPClient) -> T): T { + return try { + getOrCreateSsh().newSFTPClient().use(block) + } catch (e: Exception) { + // Connection may have gone stale — reset and retry once with a fresh connection. + runCatching { sshClient?.disconnect() } + sshClient = null + getOrCreateSsh().newSFTPClient().use(block) } } diff --git a/version.properties b/version.properties index 282f4b2..b1719d3 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ -VERSION_NAME=1.0.70 -VERSION_CODE=70 +VERSION_NAME=1.0.71 +VERSION_CODE=71