|
17 | 17 | import androidx.work.OutOfQuotaPolicy;
|
18 | 18 | import androidx.work.WorkInfo;
|
19 | 19 | import androidx.work.WorkManager;
|
| 20 | +import androidx.work.WorkQuery; |
20 | 21 |
|
21 | 22 | import org.apache.cordova.CallbackContext;
|
22 | 23 | import org.apache.cordova.CordovaPlugin;
|
|
29 | 30 | import java.io.IOException;
|
30 | 31 | import java.io.InputStreamReader;
|
31 | 32 | import java.util.ArrayList;
|
| 33 | +import java.util.Arrays; |
| 34 | +import java.util.Collections; |
32 | 35 | import java.util.HashMap;
|
33 | 36 | import java.util.Iterator;
|
34 | 37 | import java.util.List;
|
35 | 38 | import java.util.Map;
|
| 39 | +import java.util.UUID; |
36 | 40 | import java.util.concurrent.ExecutionException;
|
37 | 41 | import java.util.concurrent.Executors;
|
38 | 42 | import java.util.concurrent.ScheduledExecutorService;
|
39 | 43 | import java.util.concurrent.TimeUnit;
|
40 | 44 |
|
41 | 45 | public class FileTransferBackground extends CordovaPlugin {
|
| 46 | + |
| 47 | + private static final String TAG = "FileTransferBackground"; |
42 | 48 | public static final String WORK_TAG_UPLOAD = "work_tag_upload";
|
43 | 49 |
|
44 | 50 | private CallbackContext uploadCallback;
|
45 | 51 | private boolean ready = false;
|
46 | 52 |
|
47 | 53 | private Data httpClientBaseConfig = Data.EMPTY;
|
48 | 54 |
|
49 |
| - public static boolean workerIsStarted; |
| 55 | + private static String currentTag; |
| 56 | + private static long currentTagFetchedAt; |
50 | 57 |
|
51 | 58 | private ScheduledExecutorService executorService = null;
|
52 | 59 |
|
53 |
| - private int ccUpload; |
54 |
| - |
55 | 60 | public void sendCallback(JSONObject obj) {
|
56 | 61 | /* we check the webview has been initialized */
|
57 | 62 | if (ready) {
|
@@ -158,7 +163,8 @@ private void initManager(String options, final CallbackContext callbackContext)
|
158 | 163 |
|
159 | 164 | try {
|
160 | 165 | final JSONObject settings = new JSONObject(options);
|
161 |
| - ccUpload = settings.getInt("parallelUploadsLimit"); |
| 166 | + int ccUpload = settings.getInt("parallelUploadsLimit"); |
| 167 | + |
162 | 168 | // Rebuild base HTTP config
|
163 | 169 | httpClientBaseConfig = new Data.Builder()
|
164 | 170 | .putInt(UploadTask.KEY_INPUT_CONFIG_CONCURRENT_DOWNLOADS, ccUpload)
|
@@ -200,32 +206,31 @@ private void initManager(String options, final CallbackContext callbackContext)
|
200 | 206 | .observeForever((tasks) -> {
|
201 | 207 | int completedTasks = 0;
|
202 | 208 | 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); |
212 | 209 | switch (info.getState()) {
|
213 | 210 | // If the upload in not finished, publish its progress
|
214 | 211 | case RUNNING:
|
215 | 212 | if (info.getProgress() != Data.EMPTY) {
|
216 | 213 | String id = info.getProgress().getString(UploadTask.KEY_PROGRESS_ID);
|
217 | 214 | int progress = info.getProgress().getInt(UploadTask.KEY_PROGRESS_PERCENT, 0);
|
218 | 215 |
|
219 |
| - logMessage("initManager: " + info.getId() + " (" + info.getState() + ") Progress: " + progress); |
| 216 | + Log.d(TAG, "initManager: " + info.getId() + " (" + info.getState() + ") Progress: " + progress); |
220 | 217 | sendProgress(id, progress);
|
221 | 218 | }
|
222 | 219 | break;
|
223 | 220 | case CANCELLED:
|
224 | 221 | case BLOCKED:
|
225 | 222 | case ENQUEUED:
|
226 | 223 | case SUCCEEDED:
|
227 |
| - logMessage("Task succeeded: " + info.getId()); |
228 | 224 | 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; |
229 | 234 | case FAILED:
|
230 | 235 | // The task can't fail completely so something really bad has happened.
|
231 | 236 | logMessage("eventLabel='Uploader failed inexplicably' error='" + info.getOutputData() + "'");
|
@@ -295,67 +300,59 @@ private void addUpload(JSONObject jsonPayload) {
|
295 | 300 | } catch(PackageManager.NameNotFoundException e) {
|
296 | 301 | e.printStackTrace();
|
297 | 302 | }
|
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 |
| - ) |
| 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() |
326 | 329 | );
|
327 |
| - |
328 |
| - if (!workerIsStarted) { |
329 |
| - startWorkers(); |
330 |
| - workerIsStarted = true; |
331 |
| - } |
332 | 330 | }
|
333 | 331 |
|
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 |
| - } |
350 |
| - |
351 |
| - OneTimeWorkRequest workRequest = workRequestBuilder.build(); |
| 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); |
| 348 | + } |
352 | 349 |
|
353 |
| - WorkManager.getInstance(cordova.getContext()) |
354 |
| - .enqueueUniqueWork(FileTransferBackground.WORK_TAG_UPLOAD + "_" + i, ExistingWorkPolicy.KEEP, workRequest); |
| 350 | + OneTimeWorkRequest workRequest = workRequestBuilder.build(); |
355 | 351 |
|
356 |
| - logMessage("eventLabel=Uploader starting uploads via worker" + i); |
357 |
| - } |
| 352 | + WorkManager.getInstance(cordova.getContext()) |
| 353 | + .enqueueUniqueWork(uploadId, ExistingWorkPolicy.APPEND, workRequest); |
358 | 354 |
|
| 355 | + logMessage("eventLabel='Uploader starting upload' uploadId='" + uploadId + "'"); |
359 | 356 | }
|
360 | 357 |
|
361 | 358 | private void sendAddingUploadError(String uploadId, Exception error) {
|
@@ -433,7 +430,6 @@ private void handleAck(final Data ackData) {
|
433 | 430 | */
|
434 | 431 | private void cleanupUpload(final String uploadId) {
|
435 | 432 | final UploadEvent ack = AckDatabase.getInstance(cordova.getContext()).uploadEventDao().getById(uploadId);
|
436 |
| - |
437 | 433 | // If the upload is done there is an ACK of it, so get file name from there
|
438 | 434 | if (ack != null) {
|
439 | 435 | if (ack.getOutputData().getString(UploadTask.KEY_OUTPUT_RESPONSE_FILE) != null) {
|
@@ -505,15 +501,44 @@ public static HashMap<String, Object> convertToHashMap(JSONObject jsonObject) th
|
505 | 501 | return hashMap;
|
506 | 502 | }
|
507 | 503 |
|
508 |
| - public static void logMessage(String message) { |
509 |
| - Log.d("CordovaBackgroundUpload", message); |
| 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; |
510 | 512 | }
|
511 | 513 |
|
512 |
| - public static void logMessageInfo(String message) { |
513 |
| - Log.i("CordovaBackgroundUpload", message); |
| 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(); |
514 | 539 | }
|
515 | 540 |
|
516 |
| - public static void logMessageError(String message, Exception exception) { |
517 |
| - Log.e("CordovaBackgroundUpload", message, exception); |
| 541 | + public static void logMessage(String message) { |
| 542 | + Log.d("CordovaBackgroundUpload", message); |
518 | 543 | }
|
519 | 544 | }
|
0 commit comments