|
17 | 17 | import androidx.work.OutOfQuotaPolicy; |
18 | 18 | import androidx.work.WorkInfo; |
19 | 19 | import androidx.work.WorkManager; |
20 | | -import androidx.work.WorkQuery; |
21 | 20 |
|
22 | 21 | import org.apache.cordova.CallbackContext; |
23 | 22 | import org.apache.cordova.CordovaPlugin; |
|
30 | 29 | import java.io.IOException; |
31 | 30 | import java.io.InputStreamReader; |
32 | 31 | import java.util.ArrayList; |
33 | | -import java.util.Arrays; |
34 | | -import java.util.Collections; |
35 | 32 | import java.util.HashMap; |
36 | 33 | import java.util.Iterator; |
37 | 34 | import java.util.List; |
38 | 35 | import java.util.Map; |
39 | | -import java.util.UUID; |
40 | 36 | import java.util.concurrent.ExecutionException; |
41 | 37 | import java.util.concurrent.Executors; |
42 | 38 | import java.util.concurrent.ScheduledExecutorService; |
43 | 39 | import java.util.concurrent.TimeUnit; |
44 | 40 |
|
45 | 41 | public class FileTransferBackground extends CordovaPlugin { |
46 | | - |
47 | | - private static final String TAG = "FileTransferBackground"; |
48 | 42 | public static final String WORK_TAG_UPLOAD = "work_tag_upload"; |
49 | 43 |
|
50 | 44 | private CallbackContext uploadCallback; |
51 | 45 | private boolean ready = false; |
52 | 46 |
|
53 | 47 | private Data httpClientBaseConfig = Data.EMPTY; |
54 | 48 |
|
55 | | - private static String currentTag; |
56 | | - private static long currentTagFetchedAt; |
| 49 | + public static boolean workerIsStarted; |
57 | 50 |
|
58 | 51 | private ScheduledExecutorService executorService = null; |
59 | 52 |
|
| 53 | + private int ccUpload; |
| 54 | + |
60 | 55 | public void sendCallback(JSONObject obj) { |
61 | 56 | /* we check the webview has been initialized */ |
62 | 57 | if (ready) { |
@@ -163,8 +158,7 @@ private void initManager(String options, final CallbackContext callbackContext) |
163 | 158 |
|
164 | 159 | try { |
165 | 160 | final JSONObject settings = new JSONObject(options); |
166 | | - int ccUpload = settings.getInt("parallelUploadsLimit"); |
167 | | - |
| 161 | + ccUpload = settings.getInt("parallelUploadsLimit"); |
168 | 162 | // Rebuild base HTTP config |
169 | 163 | httpClientBaseConfig = new Data.Builder() |
170 | 164 | .putInt(UploadTask.KEY_INPUT_CONFIG_CONCURRENT_DOWNLOADS, ccUpload) |
@@ -206,31 +200,32 @@ private void initManager(String options, final CallbackContext callbackContext) |
206 | 200 | .observeForever((tasks) -> { |
207 | 201 | int completedTasks = 0; |
208 | 202 | for (WorkInfo info : tasks) { |
| 203 | + // No db in main thread |
| 204 | + executorService.schedule(() -> { |
| 205 | + final List<UploadEvent> uploadEventsList = ackDatabase |
| 206 | + .uploadEventDao() |
| 207 | + .getAll(); |
| 208 | + for (UploadEvent ack : uploadEventsList) { |
| 209 | + handleAck(ack.getOutputData()); |
| 210 | + } |
| 211 | + }, 0, TimeUnit.MILLISECONDS); |
209 | 212 | switch (info.getState()) { |
210 | 213 | // If the upload in not finished, publish its progress |
211 | 214 | case RUNNING: |
212 | 215 | if (info.getProgress() != Data.EMPTY) { |
213 | 216 | String id = info.getProgress().getString(UploadTask.KEY_PROGRESS_ID); |
214 | 217 | int progress = info.getProgress().getInt(UploadTask.KEY_PROGRESS_PERCENT, 0); |
215 | 218 |
|
216 | | - Log.d(TAG, "initManager: " + info.getId() + " (" + info.getState() + ") Progress: " + progress); |
| 219 | + logMessage("initManager: " + info.getId() + " (" + info.getState() + ") Progress: " + progress); |
217 | 220 | sendProgress(id, progress); |
218 | 221 | } |
219 | 222 | break; |
220 | 223 | case CANCELLED: |
221 | 224 | case BLOCKED: |
222 | 225 | case ENQUEUED: |
223 | 226 | case SUCCEEDED: |
| 227 | + logMessage("Task succeeded: " + info.getId()); |
224 | 228 | completedTasks++; |
225 | | - // No db in main thread |
226 | | - executorService.schedule(() -> { |
227 | | - // The corresponding ACK is already in the DB, if it not, the task is just a leftover |
228 | | - String id = info.getOutputData().getString(UploadTask.KEY_OUTPUT_ID); |
229 | | - if (ackDatabase.uploadEventDao().exists(id)) { |
230 | | - handleAck(info.getOutputData()); |
231 | | - } |
232 | | - }, 0, TimeUnit.MILLISECONDS); |
233 | | - break; |
234 | 229 | case FAILED: |
235 | 230 | // The task can't fail completely so something really bad has happened. |
236 | 231 | logMessage("eventLabel='Uploader failed inexplicably' error='" + info.getOutputData() + "'"); |
@@ -300,59 +295,67 @@ private void addUpload(JSONObject jsonPayload) { |
300 | 295 | } catch(PackageManager.NameNotFoundException e) { |
301 | 296 | e.printStackTrace(); |
302 | 297 | } |
303 | | - startUpload(uploadId, new Data.Builder() |
304 | | - // Put base info |
305 | | - .putString(UploadTask.KEY_INPUT_ID, uploadId) |
306 | | - .putString(UploadTask.KEY_INPUT_URL, (String) payload.get("serverUrl")) |
307 | | - .putString(UploadTask.KEY_INPUT_FILEPATH, (String) payload.get("filePath")) |
308 | | - .putString(UploadTask.KEY_INPUT_FILE_KEY, (String) payload.get("fileKey")) |
309 | | - .putString(UploadTask.KEY_INPUT_HTTP_METHOD, (String) payload.get("requestMethod")) |
310 | | - |
311 | | - // Put headers |
312 | | - .putInt(UploadTask.KEY_INPUT_HEADERS_COUNT, headersNames.size()) |
313 | | - .putStringArray(UploadTask.KEY_INPUT_HEADERS_NAMES, headersNames.toArray(new String[0])) |
314 | | - .putAll(headerValues) |
315 | | - |
316 | | - // Put query parameters |
317 | | - .putInt(UploadTask.KEY_INPUT_PARAMETERS_COUNT, parameterNames.size()) |
318 | | - .putStringArray(UploadTask.KEY_INPUT_PARAMETERS_NAMES, parameterNames.toArray(new String[0])) |
319 | | - .putAll(parameterValues) |
320 | | - |
321 | | - // Put notification stuff |
322 | | - .putString(UploadTask.KEY_INPUT_NOTIFICATION_TITLE, (String) payload.get("notificationTitle")) |
323 | | - .putString(UploadTask.KEY_INPUT_NOTIFICATION_ICON, cordova.getActivity().getPackageName() + ":drawable/ic_upload") |
324 | | - .putString(UploadTask.KEY_INPUT_CONFIG_INTENT_ACTIVITY, intentActivity) |
325 | | - |
326 | | - // Put config stuff |
327 | | - .putAll(httpClientBaseConfig) |
328 | | - .build() |
| 298 | + |
| 299 | + AckDatabase.getInstance(cordova.getContext()).pendingUploadDao().insert( |
| 300 | + new PendingUpload( |
| 301 | + uploadId, |
| 302 | + new Data.Builder() |
| 303 | + // Put base info |
| 304 | + .putString(UploadTask.KEY_INPUT_ID, uploadId) |
| 305 | + .putString(UploadTask.KEY_INPUT_URL, (String) payload.get("serverUrl")) |
| 306 | + .putString(UploadTask.KEY_INPUT_FILEPATH, (String) payload.get("filePath")) |
| 307 | + .putString(UploadTask.KEY_INPUT_FILE_KEY, (String) payload.get("fileKey")) |
| 308 | + .putString(UploadTask.KEY_INPUT_HTTP_METHOD, (String) payload.get("requestMethod")) |
| 309 | + // Put headers |
| 310 | + .putInt(UploadTask.KEY_INPUT_HEADERS_COUNT, headersNames.size()) |
| 311 | + .putStringArray(UploadTask.KEY_INPUT_HEADERS_NAMES, headersNames.toArray(new String[0])) |
| 312 | + .putAll(headerValues) |
| 313 | + // Put query parameters |
| 314 | + .putInt(UploadTask.KEY_INPUT_PARAMETERS_COUNT, parameterNames.size()) |
| 315 | + .putStringArray(UploadTask.KEY_INPUT_PARAMETERS_NAMES, parameterNames.toArray(new String[0])) |
| 316 | + .putAll(parameterValues) |
| 317 | + // Put notification stuff |
| 318 | + .putString(UploadTask.KEY_INPUT_NOTIFICATION_TITLE, (String) payload.get("notificationTitle")) |
| 319 | + .putString(UploadTask.KEY_INPUT_NOTIFICATION_ICON, cordova.getActivity().getPackageName() + ":drawable/ic_upload") |
| 320 | + .putString(UploadTask.KEY_INPUT_CONFIG_INTENT_ACTIVITY, intentActivity) |
| 321 | + |
| 322 | + // Put config stuff |
| 323 | + .putAll(httpClientBaseConfig) |
| 324 | + .build() |
| 325 | + ) |
329 | 326 | ); |
330 | | - } |
331 | 327 |
|
332 | | - private void startUpload(final String uploadId, final Data payload) { |
333 | | - Log.d(TAG, "startUpload: Starting work via work manager"); |
334 | | - |
335 | | - OneTimeWorkRequest.Builder workRequestBuilder = new OneTimeWorkRequest.Builder(UploadTask.class) |
336 | | - .setConstraints(new Constraints.Builder() |
337 | | - .setRequiredNetworkType(NetworkType.CONNECTED) |
338 | | - .build() |
339 | | - ) |
340 | | - .keepResultsForAtLeast(0, TimeUnit.MILLISECONDS) |
341 | | - .setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.SECONDS) |
342 | | - .addTag(FileTransferBackground.WORK_TAG_UPLOAD) |
343 | | - .addTag(getCurrentTag(cordova.getContext())) |
344 | | - .setInputData(payload); |
345 | | - |
346 | | - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { |
347 | | - workRequestBuilder.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST); |
| 328 | + if (!workerIsStarted) { |
| 329 | + startWorkers(); |
| 330 | + workerIsStarted = true; |
348 | 331 | } |
| 332 | + } |
349 | 333 |
|
350 | | - OneTimeWorkRequest workRequest = workRequestBuilder.build(); |
| 334 | + private void startWorkers() { |
| 335 | + logMessage("startUpload: Starting worker via work manager"); |
| 336 | + |
| 337 | + for (int i = 0; i < ccUpload; i++) { |
| 338 | + OneTimeWorkRequest.Builder workRequestBuilder = new OneTimeWorkRequest.Builder(UploadTask.class) |
| 339 | + .setConstraints(new Constraints.Builder() |
| 340 | + .setRequiredNetworkType(NetworkType.CONNECTED) |
| 341 | + .build() |
| 342 | + ) |
| 343 | + .keepResultsForAtLeast(0, TimeUnit.MILLISECONDS) |
| 344 | + .setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.SECONDS) |
| 345 | + .addTag(FileTransferBackground.WORK_TAG_UPLOAD); |
| 346 | + |
| 347 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { |
| 348 | + workRequestBuilder.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST); |
| 349 | + } |
351 | 350 |
|
352 | | - WorkManager.getInstance(cordova.getContext()) |
353 | | - .enqueueUniqueWork(uploadId, ExistingWorkPolicy.APPEND, workRequest); |
| 351 | + OneTimeWorkRequest workRequest = workRequestBuilder.build(); |
| 352 | + |
| 353 | + WorkManager.getInstance(cordova.getContext()) |
| 354 | + .enqueueUniqueWork(FileTransferBackground.WORK_TAG_UPLOAD + "_" + i, ExistingWorkPolicy.KEEP, workRequest); |
| 355 | + |
| 356 | + logMessage("eventLabel=Uploader starting uploads via worker" + i); |
| 357 | + } |
354 | 358 |
|
355 | | - logMessage("eventLabel='Uploader starting upload' uploadId='" + uploadId + "'"); |
356 | 359 | } |
357 | 360 |
|
358 | 361 | private void sendAddingUploadError(String uploadId, Exception error) { |
@@ -430,6 +433,7 @@ private void handleAck(final Data ackData) { |
430 | 433 | */ |
431 | 434 | private void cleanupUpload(final String uploadId) { |
432 | 435 | final UploadEvent ack = AckDatabase.getInstance(cordova.getContext()).uploadEventDao().getById(uploadId); |
| 436 | + |
433 | 437 | // If the upload is done there is an ACK of it, so get file name from there |
434 | 438 | if (ack != null) { |
435 | 439 | if (ack.getOutputData().getString(UploadTask.KEY_OUTPUT_RESPONSE_FILE) != null) { |
@@ -501,44 +505,15 @@ public static HashMap<String, Object> convertToHashMap(JSONObject jsonObject) th |
501 | 505 | return hashMap; |
502 | 506 | } |
503 | 507 |
|
504 | | - public static String getCurrentTag(Context context) { |
505 | | - final long now = System.currentTimeMillis(); |
506 | | - if (currentTag != null && now - currentTagFetchedAt <= 5000) { |
507 | | - return currentTag; |
508 | | - } |
509 | | - currentTagFetchedAt = now; |
510 | | - currentTag = fetchCurrentTag(context); |
511 | | - return currentTag; |
| 508 | + public static void logMessage(String message) { |
| 509 | + Log.d("CordovaBackgroundUpload", message); |
512 | 510 | } |
513 | 511 |
|
514 | | - public static String fetchCurrentTag(Context context) { |
515 | | - WorkQuery workQuery = WorkQuery.Builder |
516 | | - .fromTags(Arrays.asList(FileTransferBackground.WORK_TAG_UPLOAD)) |
517 | | - .addStates(Arrays.asList(WorkInfo.State.RUNNING, WorkInfo.State.ENQUEUED)) |
518 | | - .build(); |
519 | | - List<WorkInfo> workInfo; |
520 | | - try { |
521 | | - workInfo = WorkManager.getInstance(context) |
522 | | - .getWorkInfos(workQuery) |
523 | | - .get(); |
524 | | - } catch (ExecutionException | InterruptedException e) { |
525 | | - Log.w(TAG, "getForegroundInfo: Problem while retrieving task list:", e); |
526 | | - workInfo = Collections.emptyList(); |
527 | | - } |
528 | | - String prefix = "packet_"; |
529 | | - for (WorkInfo info : workInfo) { |
530 | | - if (!info.getState().isFinished()) { |
531 | | - for (String tag : info.getTags()) { |
532 | | - if (tag.startsWith(prefix)) { |
533 | | - return tag; |
534 | | - } |
535 | | - } |
536 | | - } |
537 | | - } |
538 | | - return prefix + UUID.randomUUID().toString(); |
| 512 | + public static void logMessageInfo(String message) { |
| 513 | + Log.i("CordovaBackgroundUpload", message); |
539 | 514 | } |
540 | 515 |
|
541 | | - public static void logMessage(String message) { |
542 | | - Log.d("CordovaBackgroundUpload", message); |
| 516 | + public static void logMessageError(String message, Exception exception) { |
| 517 | + Log.e("CordovaBackgroundUpload", message, exception); |
543 | 518 | } |
544 | 519 | } |
0 commit comments