@@ -17,12 +17,16 @@ import androidx.work.CoroutineWorker
1717import androidx.work.WorkerParameters
1818import com.nextcloud.client.account.User
1919import com.nextcloud.client.account.UserAccountManager
20+ import com.nextcloud.client.database.entity.UploadEntity
21+ import com.nextcloud.client.database.entity.toOCUpload
22+ import com.nextcloud.client.database.entity.toUploadEntity
2023import com.nextcloud.client.device.PowerManagementService
2124import com.nextcloud.client.jobs.BackgroundJobManager
2225import com.nextcloud.client.jobs.upload.FileUploadWorker
2326import com.nextcloud.client.network.ConnectivityService
2427import com.nextcloud.client.preferences.SubFolderRule
2528import com.nextcloud.utils.ForegroundServiceHelper
29+ import com.nextcloud.utils.extensions.updateStatus
2630import com.owncloud.android.R
2731import com.owncloud.android.datamodel.ArbitraryDataProviderImpl
2832import com.owncloud.android.datamodel.FileDataStorageManager
@@ -41,7 +45,6 @@ import com.owncloud.android.ui.notifications.NotificationUtils
4145import com.owncloud.android.utils.FileStorageUtils
4246import com.owncloud.android.utils.FilesSyncHelper
4347import com.owncloud.android.utils.MimeType
44- import com.owncloud.android.utils.MimeTypeUtil
4548import kotlinx.coroutines.Dispatchers
4649import kotlinx.coroutines.withContext
4750import java.io.File
@@ -235,37 +238,19 @@ class AutoUploadWorker(
235238 private fun getUserOrReturn (syncedFolder : SyncedFolder ): User ? {
236239 val optionalUser = userAccountManager.getUser(syncedFolder.account)
237240 if (! optionalUser.isPresent) {
238- Log_OC .w(TAG , " uploadFilesFromFolder skipped user not present" )
241+ Log_OC .w(TAG , " user not present" )
239242 return null
240243 }
241244 return optionalUser.get()
242245 }
243246
244- private fun buildPathsAndMimes (
245- paths : Set <String >,
246- syncedFolder : SyncedFolder ,
247- dateFormat : SimpleDateFormat
248- ): List <Triple <String , String , String >> {
249- val lightVersion = context.resources.getBoolean(R .bool.syncedFolder_light)
250- val currentLocale = context.resources.configuration.locales[0 ]
251-
252- return paths.map { path ->
253- val file = File (path)
254- val localPath = file.absolutePath
255- val remotePath =
256- getRemotePath(file, syncedFolder, dateFormat, lightVersion, context.resources, currentLocale)
257- val mimeType = MimeTypeUtil .getBestMimeTypeByFilename(localPath)
258- Triple (localPath, remotePath, mimeType)
259- }
260- }
261-
262247 @Suppress(" DEPRECATION" )
263248 private fun getUploadSettings (syncedFolder : SyncedFolder ): Triple <Boolean , Boolean , Int > {
264249 val lightVersion = context.resources.getBoolean(R .bool.syncedFolder_light)
265250 val accountName = syncedFolder.account
266251
267252 return if (lightVersion) {
268- Log_OC .d(TAG , " uploadFilesFromFolder light version is used" )
253+ Log_OC .d(TAG , " light version is used" )
269254 val arbitraryDataProvider = ArbitraryDataProviderImpl (context)
270255 val needsCharging = context.resources.getBoolean(R .bool.syncedFolder_light_on_charging)
271256 val needsWifi = arbitraryDataProvider.getBooleanValue(
@@ -277,7 +262,7 @@ class AutoUploadWorker(
277262 Log_OC .d(TAG , " upload action is: $uploadAction " )
278263 Triple (needsCharging, needsWifi, uploadAction)
279264 } else {
280- Log_OC .d(TAG , " getUploadSettings not light version is used" )
265+ Log_OC .d(TAG , " not light version is used" )
281266 Triple (syncedFolder.isChargingOnly, syncedFolder.isWifiOnly, syncedFolder.uploadAction)
282267 }
283268 }
@@ -286,48 +271,116 @@ class AutoUploadWorker(
286271 private suspend fun uploadFiles (syncedFolder : SyncedFolder ) = withContext(Dispatchers .IO ) {
287272 val dateFormat = prepareDateFormat()
288273 val user = getUserOrReturn(syncedFolder) ? : return @withContext
289- val paths = repository.getAutoUploadFiles(syncedFolder)
290- if (paths.isEmpty()) {
291- Log_OC .w(TAG , " uploadFiles skipped paths is empty" )
292- return @withContext
293- }
294-
295- val pathsAndMimes = buildPathsAndMimes(paths, syncedFolder, dateFormat)
296- val (needsCharging, needsWifi, uploadAction) = getUploadSettings(syncedFolder)
297-
298274 val ocAccount = OwnCloudAccount (user.toPlatformAccount(), context)
299275 val client = OwnCloudClientManagerFactory .getDefaultSingleton()
300276 .getClientFor(ocAccount, context)
277+ val lightVersion = context.resources.getBoolean(R .bool.syncedFolder_light)
278+ val currentLocale = context.resources.configuration.locales[0 ]
301279
302- pathsAndMimes.forEach { (localPath, remotePath, _) ->
303- try {
304- Log_OC .d(TAG , " creating oc upload for ${user.accountName} " )
305- val upload = OCUpload (localPath, remotePath, user.accountName).apply {
306- nameCollisionPolicy = syncedFolder.nameCollisionPolicy
307- isUseWifiOnly = needsWifi
308- isWhileChargingOnly = needsCharging
309- uploadStatus = UploadsStorageManager .UploadStatus .UPLOAD_IN_PROGRESS
310- createdBy = UploadFileOperation .CREATED_AS_INSTANT_PICTURE
311- isCreateRemoteFolder = true
312- localAction = uploadAction
280+ var lastId = 0
281+ while (true ) {
282+ val filePathsWithIds = repository.getFilePathsWithIds(syncedFolder, lastId)
283+
284+ if (filePathsWithIds.isEmpty()) {
285+ Log_OC .w(TAG , " no more files to upload at lastId: $lastId " )
286+ break
287+ }
288+ Log_OC .d(TAG , " Processing batch: lastId=$lastId , count=${filePathsWithIds.size} " )
289+
290+ filePathsWithIds.forEach { (path, id) ->
291+ val file = File (path)
292+ val localPath = file.absolutePath
293+ val remotePath = getRemotePath(
294+ file,
295+ syncedFolder,
296+ dateFormat,
297+ lightVersion,
298+ context.resources,
299+ currentLocale
300+ )
301+
302+ try {
303+ var (uploadEntity, upload) = createEntityAndUpload(user, localPath, remotePath)
304+ try {
305+ // Insert/update to IN_PROGRESS state before starting upload
306+ val generatedId = uploadsStorageManager.uploadDao.insertOrReplace(uploadEntity)
307+ uploadEntity = uploadEntity.copy(id = generatedId.toInt())
308+ upload.uploadId = generatedId
309+
310+ val operation = createUploadFileOperation(upload, user)
311+ Log_OC .d(TAG , " 🕒 uploading: $localPath , id: $generatedId " )
312+
313+ val result = operation.execute(client)
314+ uploadsStorageManager.updateStatus(uploadEntity, result.isSuccess)
315+
316+ if (result.isSuccess) {
317+ repository.markFileAsUploaded(localPath, syncedFolder)
318+ Log_OC .d(TAG , " ✅ upload completed: $localPath " )
319+ } else {
320+ Log_OC .e(
321+ TAG ,
322+ " ❌ upload failed $localPath (${upload.accountName} ): ${result.logMessage} "
323+ )
324+ }
325+ } catch (e: Exception ) {
326+ uploadsStorageManager.updateStatus(
327+ uploadEntity,
328+ UploadsStorageManager .UploadStatus .UPLOAD_FAILED
329+ )
330+ Log_OC .e(
331+ TAG ,
332+ " Exception during upload file, localPath: $localPath , remotePath: $remotePath ," +
333+ " exception: $e "
334+ )
335+ }
336+ } catch (e: Exception ) {
337+ Log_OC .e(
338+ TAG ,
339+ " Exception uploadFiles during creating entity and upload, localPath: $localPath , " +
340+ " remotePath: $remotePath , exception: $e "
341+ )
313342 }
314343
315- uploadsStorageManager.storeUpload(upload)
344+ // update last id so upload can continue where it left
345+ lastId = id
346+ }
347+ }
348+ }
349+
350+ private fun createEntityAndUpload (user : User , localPath : String , remotePath : String ): Pair <UploadEntity , OCUpload > {
351+ val (needsCharging, needsWifi, uploadAction) = getUploadSettings(syncedFolder)
352+ Log_OC .d(TAG , " creating oc upload for ${user.accountName} " )
316353
317- val operation = createUploadFileOperation(upload, user)
318- Log_OC .d(TAG , " 🕒 uploading: $localPath " )
354+ // Get or create upload entity
355+ var uploadEntity = uploadsStorageManager.uploadDao.getUploadByAccountAndPaths(
356+ localPath = localPath,
357+ remotePath = remotePath,
358+ accountName = user.accountName
359+ )
319360
320- val result = operation.execute(client)
321- if (result.isSuccess) {
322- repository.markFileAsUploaded(localPath, syncedFolder)
323- Log_OC .d(TAG , " ✅ auto upload completed: $localPath " )
324- } else {
325- Log_OC .e(TAG , " ❌ auto upload failed: $localPath " )
326- }
327- } catch (e: Exception ) {
328- Log_OC .e(TAG , " Exception uploadFiles, localPath: $localPath , remotePath: $remotePath , exception: $e " )
361+ val upload: OCUpload
362+ if (uploadEntity != null ) {
363+ // Existing upload - convert and update status
364+
365+ upload = uploadEntity.toOCUpload(null )
366+ upload.uploadStatus = UploadsStorageManager .UploadStatus .UPLOAD_IN_PROGRESS
367+ uploadEntity = upload.toUploadEntity()
368+ } else {
369+ // New upload - create with all settings
370+
371+ upload = OCUpload (localPath, remotePath, user.accountName).apply {
372+ nameCollisionPolicy = syncedFolder.nameCollisionPolicy
373+ isUseWifiOnly = needsWifi
374+ isWhileChargingOnly = needsCharging
375+ uploadStatus = UploadsStorageManager .UploadStatus .UPLOAD_IN_PROGRESS
376+ createdBy = UploadFileOperation .CREATED_AS_INSTANT_PICTURE
377+ isCreateRemoteFolder = true
378+ localAction = uploadAction
329379 }
380+ uploadEntity = upload.toUploadEntity()
330381 }
382+
383+ return uploadEntity to upload
331384 }
332385
333386 private fun createUploadFileOperation (upload : OCUpload , user : User ): UploadFileOperation = UploadFileOperation (
0 commit comments