@@ -12,17 +12,21 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager
1212import androidx.work.CoroutineWorker
1313import androidx.work.ForegroundInfo
1414import androidx.work.WorkerParameters
15+ import com.nextcloud.client.account.User
1516import com.nextcloud.client.account.UserAccountManager
1617import com.nextcloud.client.jobs.download.FileDownloadHelper
1718import com.owncloud.android.datamodel.FileDataStorageManager
1819import com.owncloud.android.datamodel.OCFile
20+ import com.owncloud.android.lib.common.OwnCloudClient
1921import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
2022import com.owncloud.android.lib.common.utils.Log_OC
2123import com.owncloud.android.operations.DownloadFileOperation
2224import com.owncloud.android.operations.DownloadType
25+ import com.owncloud.android.operations.RefreshFolderOperation
2326import com.owncloud.android.utils.FileStorageUtils
2427import com.owncloud.android.utils.theme.ViewThemeUtils
2528import kotlinx.coroutines.Dispatchers
29+ import kotlinx.coroutines.delay
2630import kotlinx.coroutines.withContext
2731import java.util.concurrent.ConcurrentHashMap
2832
@@ -40,9 +44,7 @@ class FolderDownloadWorker(
4044 const val FOLDER_ID = " FOLDER_ID"
4145 const val ACCOUNT_NAME = " ACCOUNT_NAME"
4246 const val SYNC_ALL = " SYNC_ALL"
43-
4447 private val pendingDownloads: MutableSet <Long > = ConcurrentHashMap .newKeySet()
45-
4648 fun isDownloading (id : Long ): Boolean = pendingDownloads.contains(id)
4749 }
4850
@@ -59,51 +61,65 @@ class FolderDownloadWorker(
5961
6062 val accountName = inputData.getString(ACCOUNT_NAME )
6163 if (accountName == null ) {
62- Log_OC .e(TAG , " failed accountName cannot be null" )
64+ Log_OC .e(TAG , " failed: accountName cannot be null" )
6365 return Result .failure()
6466 }
6567
6668 val optionalUser = accountManager.getUser(accountName)
6769 if (optionalUser.isEmpty) {
68- Log_OC .e(TAG , " failed user is not present" )
70+ Log_OC .e(TAG , " failed: user is not present" )
6971 return Result .failure()
7072 }
7173
7274 val syncAll = inputData.getBoolean(SYNC_ALL , false )
73-
7475 val user = optionalUser.get()
7576 storageManager = FileDataStorageManager (user, context.contentResolver)
77+
7678 val folder = storageManager.getFileById(folderID)
7779 if (folder == null ) {
78- Log_OC .e(TAG , " failed folder cannot be nul " )
80+ Log_OC .e(TAG , " failed: folder cannot be null " )
7981 return Result .failure()
8082 }
8183
82- if (syncAll) {
83- Log_OC .d(TAG , " checking folder size including all nested subfolders" )
84- if (! FileStorageUtils .checkIfEnoughSpace(folder)) {
85- notificationManager.showNotAvailableDiskSpace()
86- return Result .failure()
87- }
88- }
89-
90- Log_OC .d(TAG , " 🕒 started for ${user.accountName} downloading ${folder.fileName} " )
84+ Log_OC .d(TAG , " 🕒 started for ${user.accountName} | folder=${folder.fileName} | syncAll=$syncAll " )
9185
9286 trySetForeground(folder)
93-
9487 folderDownloadEventBroadcaster.sendDownloadEnqueued(folder.fileId)
9588 pendingDownloads.add(folder.fileId)
9689
9790 val downloadHelper = FileDownloadHelper .instance()
9891
9992 return withContext(Dispatchers .IO ) {
10093 try {
101- val files = getFiles(folder, storageManager, syncAll)
10294 val account = user.toOwnCloudAccount()
103- val client = OwnCloudClientManagerFactory .getDefaultSingleton().getClientFor(account, context)
95+ val client = OwnCloudClientManagerFactory .getDefaultSingleton()
96+ .getClientFor(account, context)
97+
98+ if (syncAll) {
99+ Log_OC .d(TAG , " checking available disk space for full recursive download" )
100+ if (! FileStorageUtils .checkIfEnoughSpace(folder)) {
101+ notificationManager.showNotAvailableDiskSpace()
102+ return @withContext Result .failure()
103+ }
104+ Log_OC .d(TAG , " 🔄 syncing full folder tree from server before collecting files" )
105+ syncFolderRecursivelyFromServer(folder, user, client)
106+ }
107+
108+ val files = getFiles(folder, storageManager, syncAll)
109+ if (files.isEmpty()) {
110+ Log_OC .d(TAG , " ✅ no files need downloading" )
111+ notificationManager.showCompletionNotification(folder.fileName, true )
112+ return @withContext Result .success()
113+ }
114+
115+ var overallSuccess = true
104116
105- var result = true
106117 files.forEachIndexed { index, file ->
118+ if (isStopped) {
119+ Log_OC .d(TAG , " ⚠️ worker stopped mid-download, aborting remaining files" )
120+ return @withContext Result .failure()
121+ }
122+
107123 if (! FileStorageUtils .checkIfEnoughSpace(folder)) {
108124 notificationManager.showNotAvailableDiskSpace()
109125 return @withContext Result .failure()
@@ -117,46 +133,92 @@ class FolderDownloadWorker(
117133 files.size
118134 )
119135 notificationManager.showNotification(notification)
120-
121- val foregroundInfo = notificationManager.getForegroundInfo(notification)
122- setForeground(foregroundInfo)
136+ setForeground(notificationManager.getForegroundInfo(notification))
123137 }
124138
125139 val operation = DownloadFileOperation (user, file, context)
126140 val operationResult = operation.execute(client)
141+
127142 if (operationResult?.isSuccess == true && operation.downloadType == = DownloadType .DOWNLOAD ) {
128143 getOCFile(operation)?.let { ocFile ->
129144 downloadHelper.saveFile(ocFile, operation, storageManager)
130145 }
131146 }
132147
133- if (! operationResult.isSuccess) {
134- result = false
148+ if (operationResult?.isSuccess != true ) {
149+ Log_OC .w(TAG , " ⚠️ download failed for ${file.remotePath} : ${operationResult?.logMessage} " )
150+ overallSuccess = false
135151 }
136152 }
137153
138- withContext(Dispatchers .Main ) {
139- notificationManager.showCompletionNotification(folder.fileName, result)
140- }
154+ notificationManager.showCompletionNotification(folder.fileName, overallSuccess)
141155
142- if (result ) {
143- Log_OC .d(TAG , " ✅ completed" )
156+ if (overallSuccess ) {
157+ Log_OC .d(TAG , " ✅ completed successfully " )
144158 Result .success()
145159 } else {
146- Log_OC .d(TAG , " ❌ failed " )
160+ Log_OC .d(TAG , " ❌ completed with failures " )
147161 Result .failure()
148162 }
149163 } catch (e: Exception ) {
150- Log_OC .d(TAG , " ❌ failed reason: $e " )
164+ Log_OC .e(TAG , " ❌ unexpected failure: $e " )
165+ notificationManager.showCompletionNotification(folder.fileName, false )
151166 Result .failure()
152167 } finally {
153168 folderDownloadEventBroadcaster.sendDownloadCompleted(folder.fileId)
154169 pendingDownloads.remove(folder.fileId)
170+
171+ // delay so that user can see the error or success notification
172+ delay(2000 )
155173 notificationManager.dismiss()
156174 }
157175 }
158176 }
159177
178+ private fun syncFolderRecursivelyFromServer (folder : OCFile , user : User , client : OwnCloudClient ) {
179+ if (isStopped) return
180+
181+ try {
182+ Log_OC .d(TAG , " 🔄 refreshing from server: ${folder.remotePath} " )
183+
184+ val operation = RefreshFolderOperation (
185+ folder,
186+ System .currentTimeMillis(),
187+ false ,
188+ true ,
189+ false ,
190+ storageManager,
191+ user,
192+ context
193+ )
194+
195+ val result = operation.execute(client)
196+
197+ if (! result.isSuccess) {
198+ Log_OC .w(TAG , " ⚠️ failed to refresh ${folder.remotePath} : ${result.logMessage} " )
199+ return
200+ }
201+
202+ val refreshedFolder = storageManager.getFileById(folder.fileId) ? : run {
203+ Log_OC .w(TAG , " ⚠️ folder ${folder.fileId} missing from DB after refresh" )
204+ return
205+ }
206+
207+ val subFolders = storageManager
208+ .getFolderContent(refreshedFolder, false )
209+ .filter { it.isFolder }
210+
211+ for (subFolder in subFolders) {
212+ if (isStopped) return
213+ syncFolderRecursivelyFromServer(subFolder, user, client)
214+ }
215+ } catch (e: Exception ) {
216+ Log_OC .w(TAG , " ⚠️ exception syncing ${folder.remotePath} : $e " )
217+ } finally {
218+ Log_OC .d(TAG , " sub folders are fetched before downloading all" )
219+ }
220+ }
221+
160222 @Suppress(" ReturnCount" )
161223 override suspend fun getForegroundInfo (): ForegroundInfo {
162224 return try {
@@ -167,11 +229,12 @@ class FolderDownloadWorker(
167229 return notificationManager.getForegroundInfo(null )
168230 }
169231
170- val folder = storageManager.getFileById(folderID) ? : return notificationManager.getForegroundInfo(null )
232+ val folder = storageManager.getFileById(folderID)
233+ ? : return notificationManager.getForegroundInfo(null )
171234
172- return notificationManager.getForegroundInfo(folder)
235+ notificationManager.getForegroundInfo(folder)
173236 } catch (e: Exception ) {
174- Log_OC .w(TAG , " ⚠️ Error getting foreground info: ${e.message} " )
237+ Log_OC .w(TAG , " ⚠️ error getting foreground info: ${e.message} " )
175238 notificationManager.getForegroundInfo(null )
176239 }
177240 }
@@ -181,20 +244,18 @@ class FolderDownloadWorker(
181244 val foregroundInfo = notificationManager.getForegroundInfo(folder)
182245 setForeground(foregroundInfo)
183246 } catch (e: Exception ) {
184- Log_OC .w(TAG , " ⚠️ Could not set foreground service: ${e.message} " )
247+ Log_OC .w(TAG , " ⚠️ could not set foreground service: ${e.message} " )
185248 }
186249 }
187250
188- private fun getOCFile (operation : DownloadFileOperation ): OCFile ? {
189- val file = operation.file?.fileId?.let { storageManager.getFileById(it) }
190- ? : storageManager.getFileByDecryptedRemotePath(operation.file?.remotePath)
191- ? : run {
192- Log_OC .e(TAG , " could not save ${operation.file?.remotePath} " )
193- return null
194- }
195-
196- return file
251+ private fun getOCFile (operation : DownloadFileOperation ): OCFile ? = operation.file?.fileId?.let {
252+ storageManager.getFileById(it)
197253 }
254+ ? : storageManager.getFileByDecryptedRemotePath(operation.file?.remotePath)
255+ ? : run {
256+ Log_OC .e(TAG , " could not resolve OCFile for save: ${operation.file?.remotePath} " )
257+ null
258+ }
198259
199260 private fun getFiles (folder : OCFile , storageManager : FileDataStorageManager , syncAll : Boolean ): List <OCFile > =
200261 if (syncAll) {
0 commit comments