@@ -19,6 +19,7 @@ import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
1919import com.mapbox.navigation.copilot.AttachmentMetadata
2020import com.mapbox.navigation.copilot.HistoryAttachmentsUtils
2121import com.mapbox.navigation.copilot.HistoryAttachmentsUtils.attachmentFilename
22+ import com.mapbox.navigation.copilot.HistoryAttachmentsUtils.create
2223import com.mapbox.navigation.copilot.HistoryAttachmentsUtils.generateSessionId
2324import com.mapbox.navigation.copilot.HistoryAttachmentsUtils.rename
2425import com.mapbox.navigation.copilot.HttpServiceProvider
@@ -35,6 +36,7 @@ import com.mapbox.navigation.copilot.internal.CopilotSession
3536import com.mapbox.navigation.copilot.internal.PushStatus
3637import com.mapbox.navigation.copilot.internal.saveFilename
3738import com.mapbox.navigation.utils.internal.logD
39+ import com.mapbox.navigation.utils.internal.logE
3840import com.mapbox.navigation.utils.internal.logW
3941import kotlinx.coroutines.Dispatchers
4042import kotlinx.coroutines.suspendCancellableCoroutine
@@ -48,7 +50,7 @@ import kotlin.coroutines.resume
4850 */
4951@OptIn(ExperimentalPreviewMapboxNavigationAPI ::class )
5052internal class HistoryUploadWorker (
51- context : Context ,
53+ private val context : Context ,
5254 private val workerParams : WorkerParameters ,
5355) : CoroutineWorker(context, workerParams) {
5456
@@ -65,14 +67,29 @@ internal class HistoryUploadWorker(
6567 val copilotSession = copilotSessionFrom(workerParams.inputData)
6668 val recordingFile =
6769 rename(File (copilotSession.recording), attachmentFilename(copilotSession))
68- val sessionFile = File (recordingFile.parent, copilotSession.saveFilename())
70+ val sessionFile = create(recordingFile.parent, copilotSession.saveFilename())
71+
72+ logD(
73+ " HistoryUploadWorker.doWork(). " +
74+ " Session: $copilotSession , " +
75+ " runAttemptCount: $runAttemptCount " ,
76+ )
77+
78+ if (copilotSession.endedAt.isEmpty()) {
79+ reportCopilotError(
80+ " Passed copilot session has empty endedAt date: $copilotSession , " +
81+ " Copilot dir files: ${recordingFile.parentFile?.listFiles()?.map { it.name }} " ,
82+ )
83+ }
6984
7085 if (! recordingFile.exists()) {
7186 reportCopilotError(
7287 " History file does not exist: ${recordingFile.name} . " +
7388 " Copilot session: $copilotSession . " +
7489 " Copilot dir files: ${recordingFile.parentFile?.listFiles()?.map { it.name }} " ,
7590 )
91+ cleanup(copilotSession, recordingFile, sessionFile)
92+ return @withContext Result .failure()
7693 }
7794
7895 if (! sessionFile.exists()) {
@@ -107,15 +124,13 @@ internal class HistoryUploadWorker(
107124 if (uploadHistoryFile(uploadOptions)) {
108125 success(copilotSession)
109126 logD(" Result.success(${recordingFile.name} |${sessionFile.name} )" )
110- delete(recordingFile)
111- delete(sessionFile)
127+ cleanup(copilotSession, recordingFile, sessionFile)
112128 Result .success()
113129 } else {
114130 failure(copilotSession)
115131 if (runAttemptCount >= MAX_RUN_ATTEMPT_COUNT ) {
116132 logW(" Result.failure(${recordingFile.name} |${sessionFile.name} )" )
117- delete(recordingFile)
118- delete(sessionFile)
133+ cleanup(copilotSession, recordingFile, sessionFile)
119134 Result .failure()
120135 } else {
121136 logD(
@@ -129,9 +144,37 @@ internal class HistoryUploadWorker(
129144 }
130145 }
131146
132- private fun delete (file : File ) {
147+ private fun cleanup (
148+ copilotSession : CopilotSession ,
149+ recordingFile : File ,
150+ sessionFile : File ,
151+ ) {
152+ if (! delete(sessionFile)) {
153+ reportCopilotError(" Can't delete session file" )
154+ }
155+
156+ if (! delete(recordingFile)) {
157+ reportCopilotError(" Can't delete recording file" )
158+ }
159+
160+ /* *
161+ * Cancels any pending upload jobs that may have been scheduled by
162+ * [PeriodicHistoryCleanupWorker].
163+ *
164+ * NOTE: TODO This does not fully guarantee that a new job won’t be scheduled if
165+ * [PeriodicHistoryCleanupWorker] is currently running.
166+ */
167+ cancelScheduledUploading(context, copilotSession)
168+ }
169+
170+ private fun delete (file : File ): Boolean {
133171 logD(" Deleting ${file.name} " )
134- HistoryAttachmentsUtils .delete(file)
172+ return try {
173+ HistoryAttachmentsUtils .delete(file)
174+ } catch (e: SecurityException ) {
175+ logE(" Deleting ${file.name} error: $e " )
176+ false
177+ }
135178 }
136179
137180 private suspend fun uploadHistoryFile (
@@ -215,6 +258,7 @@ internal class HistoryUploadWorker(
215258
216259 private fun logD (msg : String ) = logD(" [upload] [$id ] $msg " , LOG_CATEGORY )
217260 private fun logW (msg : String ) = logW(" [upload] [$id ] $msg " , LOG_CATEGORY )
261+ private fun logE (msg : String ) = logE(" [upload] [$id ] $msg " , LOG_CATEGORY )
218262
219263 internal companion object {
220264
@@ -235,23 +279,30 @@ internal class HistoryUploadWorker(
235279
236280 /* *
237281 * Max backoff delay is limited by [WorkRequest.MAX_BACKOFF_MILLIS] (5 hours = 18 000 seconds).
282+ * With [MAX_RUN_ATTEMPT_COUNT] = 15, there are 15 total attempts (1 initial + 14 retries).
238283 * The total cumulative retry time could be approximately 20 hours
239284 * (it can be more depending on system delays):
240285 *
241- * 300 * 2⁰ = 300
242- * 300 * 2¹ = 600
243- * 300 * 2² = 1200
244- * 300 * 2³ = 2400
245- * 300 * 2⁴ = 4800
246- * 300 * 2⁵ = 9600
247- * 300 * 2⁶ = 19200 -> capped to 18000
248- * 300 * 2⁷ = 38400 -> capped to 18000
249- * 300 × 2⁸ = 76800 -> caped to 18000
286+ * Attempt 1: runs immediately (no delay)
287+ * Delay before attempt 2: 10 * 2⁰ = 10
288+ * Delay before attempt 3: 10 * 2¹ = 20
289+ * Delay before attempt 4: 10 * 2² = 40
290+ * Delay before attempt 5: 10 * 2³ = 80
291+ * Delay before attempt 6: 10 * 2⁴ = 160
292+ * Delay before attempt 7: 10 * 2⁵ = 320
293+ * Delay before attempt 8: 10 * 2⁶ = 640
294+ * Delay before attempt 9: 10 * 2⁷ = 1280
295+ * Delay before attempt 10: 10 * 2⁸ = 2560
296+ * Delay before attempt 11: 10 * 2⁹ = 5120
297+ * Delay before attempt 12: 10 * 2¹⁰ = 10240
298+ * Delay before attempt 13: 10 * 2¹¹ = 20480 -> capped to 18000
299+ * Delay before attempt 14: 10 * 2¹² = 40960 -> capped to 18000
300+ * Delay before attempt 15: 10 * 2¹³ = 81920 -> capped to 18000
250301 *
251- * Total delay is 72900 seconds ≈ 20 hours
302+ * Total cumulative delay is 74470 seconds ≈ 20 hours
252303 */
253- const val MAX_RUN_ATTEMPT_COUNT = 9
254- private const val DELAY_IN_SECONDS = 300L // 5 minutes
304+ const val MAX_RUN_ATTEMPT_COUNT = 15
305+ private const val DELAY_IN_SECONDS = 10L
255306
256307 /* *
257308 * uploadHistory
@@ -260,7 +311,6 @@ internal class HistoryUploadWorker(
260311 context : Context ,
261312 copilotSession : CopilotSession ,
262313 ) {
263- val workName = " copilot-upload.${copilotSession.recording} "
264314 val workRequest = OneTimeWorkRequestBuilder <HistoryUploadWorker >()
265315 .setConstraints(requireInternet())
266316 .setBackoffCriteria(BackoffPolicy .EXPONENTIAL , DELAY_IN_SECONDS , TimeUnit .SECONDS )
@@ -269,9 +319,20 @@ internal class HistoryUploadWorker(
269319 .build()
270320
271321 WorkManager .getInstance(context)
272- .enqueueUniqueWork(workName, ExistingWorkPolicy .KEEP , workRequest)
322+ .enqueueUniqueWork(
323+ copilotSession.workName(),
324+ ExistingWorkPolicy .KEEP ,
325+ workRequest,
326+ )
273327 }
274328
329+ fun cancelScheduledUploading (context : Context , copilotSession : CopilotSession ) {
330+ WorkManager .getInstance(context)
331+ .cancelUniqueWork(copilotSession.workName())
332+ }
333+
334+ private fun CopilotSession.workName () = " copilot-upload.$recording "
335+
275336 private fun requireInternet () =
276337 Constraints .Builder ().setRequiredNetworkType(NetworkType .CONNECTED ).build()
277338
0 commit comments