@@ -22,6 +22,8 @@ import com.nextcloud.client.preferences.AppPreferences
22
22
import com.nextcloud.model.WorkerState
23
23
import com.nextcloud.model.WorkerStateLiveData
24
24
import com.nextcloud.utils.extensions.getPercent
25
+ import com.nextcloud.utils.ForegroundServiceHelper
26
+ import com.owncloud.android.datamodel.ForegroundServiceType
25
27
import com.owncloud.android.datamodel.FileDataStorageManager
26
28
import com.owncloud.android.datamodel.ThumbnailsCacheManager
27
29
import com.owncloud.android.datamodel.UploadsStorageManager
@@ -86,13 +88,17 @@ class FileUploadWorker(
86
88
}
87
89
88
90
private var lastPercent = 0
89
- private val notificationManager = UploadNotificationManager (context, viewThemeUtils, Random .nextInt())
91
+ private var notificationManager = UploadNotificationManager (context, viewThemeUtils, Random .nextInt())
90
92
private val intents = FileUploaderIntents (context)
91
93
private val fileUploaderDelegate = FileUploaderDelegate ()
92
94
93
95
@Suppress(" TooGenericExceptionCaught" )
94
96
override fun doWork (): Result = try {
95
97
Log_OC .d(TAG , " FileUploadWorker started" )
98
+
99
+ // Set as foreground service for long-running uploads (prevents Android from killing the worker)
100
+ setForegroundAsync(createForegroundInfo())
101
+
96
102
backgroundJobManager.logStartOfWorker(BackgroundJobManagerImpl .formatClassTag(this ::class ))
97
103
val result = uploadFiles()
98
104
backgroundJobManager.logEndOfWorker(BackgroundJobManagerImpl .formatClassTag(this ::class ), result)
@@ -160,15 +166,32 @@ class FileUploadWorker(
160
166
val operation = createUploadFileOperation(upload, user.get())
161
167
currentUploadFileOperation = operation
162
168
169
+ // Create deterministic notification manager for this specific file
170
+ notificationManager = UploadNotificationManager (
171
+ context,
172
+ viewThemeUtils,
173
+ generateDeterministicNotificationId(upload.localPath, upload.fileSize)
174
+ )
175
+
176
+ // Show notification only when upload is about to start (not for queued files)
177
+ Log_OC .d(TAG , " 📋 Queued: ${upload.localPath} (${index + 1 } /${totalUploadSize} ) - About to start upload" )
163
178
notificationManager.prepareForStart(
164
179
operation,
165
180
cancelPendingIntent = intents.startIntent(operation),
166
181
startIntent = intents.notificationStartIntent(operation),
167
- currentUploadIndex = index,
182
+ currentUploadIndex = index + 1 , // Show 1-based index for user
168
183
totalUploadSize = totalUploadSize
169
184
)
185
+ Log_OC .d(TAG , " 🔔 Notification shown for: ${upload.localPath} " )
170
186
187
+ Log_OC .d(TAG , " 🚀 STARTING UPLOAD: ${upload.localPath} " )
171
188
val result = upload(operation, user.get())
189
+ Log_OC .d(TAG , " ✅ FINISHED UPLOAD: ${upload.localPath} - Result: ${result.isSuccess} " )
190
+
191
+ // Dismiss notification after upload completes
192
+ notificationManager.dismissNotification()
193
+ Log_OC .d(TAG , " 🔕 Notification dismissed for: ${upload.localPath} " )
194
+
172
195
currentUploadFileOperation = null
173
196
174
197
fileUploaderDelegate.sendBroadcastUploadFinished(
@@ -360,4 +383,54 @@ class FileUploadWorker(
360
383
361
384
lastPercent = percent
362
385
}
386
+
387
+ /* *
388
+ * Generate a deterministic notification ID based on file characteristics.
389
+ * This ensures the same file always gets the same notification ID,
390
+ * preventing duplicate notifications for resumed uploads.
391
+ */
392
+ private fun generateDeterministicNotificationId (localPath : String , fileSize : Long ): Int {
393
+ return try {
394
+ // Use same logic as session ID generation for consistency
395
+ val file = java.io.File (localPath)
396
+ val canonicalPath = file.canonicalPath
397
+ val baseString = " ${canonicalPath} _$fileSize "
398
+
399
+ // Generate deterministic hash and ensure it's positive for notification ID
400
+ val hash = baseString.hashCode()
401
+ val notificationId = Math .abs(hash)
402
+
403
+ Log_OC .d(TAG , " generateDeterministicNotificationId: Generated notification ID: $notificationId for file: $canonicalPath (size: $fileSize )" )
404
+ notificationId
405
+ } catch (e: Exception ) {
406
+ Log_OC .e(TAG , " generateDeterministicNotificationId: Error generating deterministic notification ID, falling back to random" , e)
407
+ Random .nextInt()
408
+ }
409
+ }
410
+
411
+ /* *
412
+ * Create foreground info for long-running upload tasks.
413
+ * This ensures uploads continue even when app is closed.
414
+ */
415
+ private fun createForegroundInfo () = try {
416
+ val notification = notificationManager.notificationBuilder.build()
417
+ val notificationId = Random .nextInt() // Will be replaced by deterministic ID when upload starts
418
+
419
+ Log_OC .d(TAG , " createForegroundInfo: Creating foreground service for upload with notification ID: $notificationId " )
420
+
421
+ ForegroundServiceHelper .createWorkerForegroundInfo(
422
+ notificationId,
423
+ notification,
424
+ ForegroundServiceType .DataSync
425
+ )
426
+ } catch (e: Exception ) {
427
+ Log_OC .e(TAG , " createForegroundInfo: Error creating foreground info" , e)
428
+ // Fallback to default notification
429
+ val notification = notificationManager.notificationBuilder.build()
430
+ ForegroundServiceHelper .createWorkerForegroundInfo(
431
+ Random .nextInt(),
432
+ notification,
433
+ ForegroundServiceType .DataSync
434
+ )
435
+ }
363
436
}
0 commit comments