Compare commits

...

3 Commits

Author SHA1 Message Date
amir 812b40b42f v1.0.72: raise WebDAV timeout from 30s to 5min for large video uploads
Build & Release APK / build (push) Successful in 13m11s
30s read/write timeout killed uploads of large video files mid-stream.
Videos in zahra's folders took 56s+ to upload — anything over 30s was
failing and counted as a failed file (PARTIAL). Raised to 5 minutes.
2026-06-07 02:44:19 +00:00
amir b7ec3f4ad3 v1.0.71: SFTP connection pooling — reuse SSH session across all operations
Build & Release APK / build (push) Failing after 20m59s
Previously every listFiles/uploadFile/downloadFile/deleteFile call created
a fresh SSH connection (connect → auth → use → disconnect). For zahra's
folder with 69 subdirectories, the recursive listing alone made 70 full
SSH handshakes, then one more per downloaded file — causing connection
timeouts and 65 upload/download failures reported as PARTIAL.

Now the provider holds a persistent SSH session and reuses it for all
calls, reconnecting automatically if the connection drops.
2026-06-07 02:34:01 +00:00
amir 537808ca10 v1.0.70: single-source version (name always tracks build number)
Build & Release APK / build (push) Successful in 12m58s
versionName is now derived as 1.0.<versionCode>, so the git tag, APK filename,
and in-app About version are always the same number and can't drift.
2026-06-07 02:08:10 +00:00
4 changed files with 35 additions and 14 deletions
+4 -1
View File
@@ -33,7 +33,10 @@ android {
minSdk = 26 minSdk = 26
targetSdk = 35 targetSdk = 35
versionCode = versionProps["VERSION_CODE"].toString().toInt() versionCode = versionProps["VERSION_CODE"].toString().toInt()
versionName = versionProps["VERSION_NAME"].toString() // Single source of truth: the human version always tracks the build number, so the
// git tag (v1.0.N), the APK filename, and the in-app "About" all read 1.0.N and
// can never drift apart again. Bump only VERSION_CODE in version.properties.
versionName = "1.0.${versionProps["VERSION_CODE"].toString().toInt()}"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
// Placeholder — replace with real keys before release // Placeholder — replace with real keys before release
@@ -23,19 +23,36 @@ class SftpProvider(private val account: CloudAccount, private val credentialStor
private val password = creds["password"]?.jsonPrimitive?.content private val password = creds["password"]?.jsonPrimitive?.content
private val privateKey = creds["private_key"]?.jsonPrimitive?.content private val privateKey = creds["private_key"]?.jsonPrimitive?.content
private fun <T> 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() val ssh = SSHClient()
ssh.addHostKeyVerifier(TofuHostKeyVerifier(credentialStore)) ssh.addHostKeyVerifier(TofuHostKeyVerifier(credentialStore))
ssh.connect(host, port) ssh.connect(host, port)
try { if (!privateKey.isNullOrBlank()) {
if (!privateKey.isNullOrBlank()) { ssh.authPublickey(username, ssh.loadKeys(privateKey, null, null))
ssh.authPublickey(username, ssh.loadKeys(privateKey, null, null)) } else {
} else { ssh.authPassword(username, password ?: "")
ssh.authPassword(username, password ?: "") }
} sshClient = ssh
return ssh.newSFTPClient().use(block) return ssh
} finally { }
ssh.disconnect()
private fun <T> 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)
} }
} }
@@ -35,7 +35,8 @@ open class WebDavProvider(protected val account: CloudAccount) : CloudProvider {
val pass = creds["password"]?.jsonPrimitive?.content ?: "" val pass = creds["password"]?.jsonPrimitive?.content ?: ""
OkHttpClient.Builder() OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS) .connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS) .readTimeout(5, TimeUnit.MINUTES)
.writeTimeout(5, TimeUnit.MINUTES)
.addInterceptor { chain -> .addInterceptor { chain ->
val req = chain.request().newBuilder() val req = chain.request().newBuilder()
.header("Authorization", Credentials.basic(user, pass)) .header("Authorization", Credentials.basic(user, pass))
+2 -2
View File
@@ -1,2 +1,2 @@
VERSION_NAME=1.0.68 VERSION_NAME=1.0.72
VERSION_CODE=69 VERSION_CODE=72