Skip to content

Commit 86f66cd

Browse files
committed
feat(file-list): folder refresh scheduler
Signed-off-by: alperozturk96 <alper_ozturk@proton.me>
1 parent 48bea3c commit 86f66cd

File tree

2 files changed

+99
-1
lines changed

2 files changed

+99
-1
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
package com.owncloud.android.operations
9+
10+
import androidx.lifecycle.lifecycleScope
11+
import com.owncloud.android.lib.common.operations.RemoteOperationResult
12+
import com.owncloud.android.lib.common.utils.Log_OC
13+
import com.owncloud.android.lib.resources.files.CheckEtagRemoteOperation
14+
import com.owncloud.android.ui.activity.FileDisplayActivity
15+
import kotlinx.coroutines.Dispatchers
16+
import kotlinx.coroutines.Job
17+
import kotlinx.coroutines.delay
18+
import kotlinx.coroutines.launch
19+
import kotlinx.coroutines.withContext
20+
21+
class FolderRefreshScheduler(private val activity: FileDisplayActivity) {
22+
companion object {
23+
private const val ETAG_POLL_INTERVAL_MS = 30_000L
24+
private const val TAG = "FolderRefreshScheduler"
25+
}
26+
27+
private var job: Job? = null
28+
29+
fun start() {
30+
stop()
31+
32+
job = activity.lifecycleScope.launch {
33+
while (true) {
34+
delay(ETAG_POLL_INTERVAL_MS)
35+
checkAndRefreshIfETagChanged()
36+
}
37+
}
38+
39+
Log_OC.d(TAG, "ETag polling started (interval=${ETAG_POLL_INTERVAL_MS}ms)")
40+
}
41+
42+
fun stop() {
43+
job?.cancel()
44+
job = null
45+
Log_OC.d(TAG, "ETag polling stopped")
46+
}
47+
48+
@Suppress("ReturnCount", "TooGenericExceptionCaught")
49+
private suspend fun checkAndRefreshIfETagChanged() {
50+
if (activity.isFinishing || activity.isSearchOpen()) return
51+
52+
val fragment = activity.listOfFilesFragment ?: return
53+
if (fragment.isSearchFragment) return
54+
55+
val currentDir = activity.getCurrentDir() ?: return
56+
val currentUser = activity.user.orElse(null) ?: return
57+
58+
val localEtag = currentDir.etag ?: ""
59+
60+
Log_OC.d(TAG, "eTag poll → checking '${currentDir.remotePath}' (local eTag='$localEtag')")
61+
62+
val result = withContext(Dispatchers.IO) {
63+
try {
64+
CheckEtagRemoteOperation(currentDir.remotePath, localEtag).execute(currentUser, activity)
65+
} catch (e: Exception) {
66+
Log_OC.e(TAG, "eTag poll: could not create client — ${e.message}")
67+
null
68+
}
69+
} ?: return
70+
71+
when (result.code) {
72+
RemoteOperationResult.ResultCode.ETAG_CHANGED -> {
73+
Log_OC.i(TAG, "eTag poll → eTag changed for '${currentDir.remotePath}', triggering sync")
74+
activity.startSyncFolderOperation(currentDir, ignoreETag = true)
75+
}
76+
77+
RemoteOperationResult.ResultCode.ETAG_UNCHANGED -> {
78+
Log_OC.d(TAG, "eTag poll → no change for '${currentDir.remotePath}'")
79+
}
80+
81+
RemoteOperationResult.ResultCode.FILE_NOT_FOUND -> {
82+
Log_OC.w(TAG, "eTag poll → directory not found on server, refreshing to handle deletion")
83+
activity.startSyncFolderOperation(currentDir, ignoreETag = true)
84+
}
85+
86+
else -> {
87+
Log_OC.w(TAG, "eTag poll → unexpected result code: ${result.code}")
88+
}
89+
}
90+
}
91+
}

app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ import com.owncloud.android.lib.resources.notifications.GetNotificationsRemoteOp
102102
import com.owncloud.android.operations.CopyFileOperation
103103
import com.owncloud.android.operations.CreateFolderOperation
104104
import com.owncloud.android.operations.DownloadType
105+
import com.owncloud.android.operations.FolderRefreshScheduler
105106
import com.owncloud.android.operations.MoveFileOperation
106107
import com.owncloud.android.operations.RefreshFolderOperation
107108
import com.owncloud.android.operations.RemoveFileOperation
@@ -252,6 +253,8 @@ class FileDisplayActivity :
252253
*/
253254
private var fileIDForImmediatePreview: Long = -1
254255

256+
private lateinit var folderRefreshScheduler: FolderRefreshScheduler
257+
255258
fun setFileIDForImmediatePreview(fileIDForImmediatePreview: Long) {
256259
this.fileIDForImmediatePreview = fileIDForImmediatePreview
257260
}
@@ -264,6 +267,7 @@ class FileDisplayActivity :
264267

265268
super.onCreate(savedInstanceState)
266269
lastDisplayedAccountName = preferences.lastDisplayedAccountName
270+
folderRefreshScheduler = FolderRefreshScheduler(this)
267271

268272
intent?.let {
269273
handleCommonIntents(it)
@@ -1179,7 +1183,7 @@ class FileDisplayActivity :
11791183
uploader.uploadUris()
11801184
}
11811185

1182-
private fun isSearchOpen(): Boolean {
1186+
fun isSearchOpen(): Boolean {
11831187
if (searchView == null) {
11841188
return false
11851189
} else {
@@ -1365,6 +1369,8 @@ class FileDisplayActivity :
13651369

13661370
super.onResume()
13671371

1372+
folderRefreshScheduler.start()
1373+
13681374
if (ocFileListFragment?.isSearchFragment == true) {
13691375
ocFileListFragment?.setSearchArgs(ocFileListFragment?.arguments)
13701376
}
@@ -1476,6 +1482,7 @@ class FileDisplayActivity :
14761482

14771483
override fun onStop() {
14781484
Log_OC.v(TAG, "onStop()")
1485+
folderRefreshScheduler.stop()
14791486
unregisterReceivers()
14801487
super.onStop()
14811488
}

0 commit comments

Comments
 (0)