From 1cb121bfcd6a6e39f24677ccbb9c32a45ec1ad79 Mon Sep 17 00:00:00 2001
From: Dinitri Ragoo <42667690+dinitri@users.noreply.github.com>
Date: Wed, 11 Oct 2023 14:45:10 +0400
Subject: [PATCH 1/6] Revert "Manage WorkManager: One Running Worker to upload
a Batch of Files"
---
plugin.xml | 2 -
src/android/AckDatabase.java | 4 +-
src/android/FileTransferBackground.java | 179 +++++-----
src/android/PendingUpload.java | 48 ---
src/android/PendingUploadDao.java | 59 ----
src/android/UploadEventDao.java | 8 +-
src/android/UploadForegroundNotification.java | 34 +-
src/android/UploadNotification.java | 27 +-
src/android/UploadTask.java | 309 +++++++++---------
9 files changed, 314 insertions(+), 356 deletions(-)
delete mode 100644 src/android/PendingUpload.java
delete mode 100644 src/android/PendingUploadDao.java
diff --git a/plugin.xml b/plugin.xml
index a2b5418b..6b68bed6 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -47,8 +47,6 @@
-
-
diff --git a/src/android/AckDatabase.java b/src/android/AckDatabase.java
index 18ff92ad..f528b276 100644
--- a/src/android/AckDatabase.java
+++ b/src/android/AckDatabase.java
@@ -8,7 +8,7 @@
import androidx.room.TypeConverters;
import androidx.work.Data;
-@Database(entities = {UploadEvent.class, PendingUpload.class}, version = 4)
+@Database(entities = {UploadEvent.class}, version = 3)
@TypeConverters(value = {Data.class})
public abstract class AckDatabase extends RoomDatabase {
private static AckDatabase instance;
@@ -30,6 +30,4 @@ public static void closeInstance() {
}
public abstract UploadEventDao uploadEventDao();
-
- public abstract PendingUploadDao pendingUploadDao();
}
diff --git a/src/android/FileTransferBackground.java b/src/android/FileTransferBackground.java
index bdd82da4..8cb6c393 100644
--- a/src/android/FileTransferBackground.java
+++ b/src/android/FileTransferBackground.java
@@ -17,6 +17,7 @@
import androidx.work.OutOfQuotaPolicy;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;
+import androidx.work.WorkQuery;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
@@ -29,16 +30,21 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class FileTransferBackground extends CordovaPlugin {
+
+ private static final String TAG = "FileTransferBackground";
public static final String WORK_TAG_UPLOAD = "work_tag_upload";
private CallbackContext uploadCallback;
@@ -46,12 +52,11 @@ public class FileTransferBackground extends CordovaPlugin {
private Data httpClientBaseConfig = Data.EMPTY;
- public static boolean workerIsStarted;
+ private static String currentTag;
+ private static long currentTagFetchedAt;
private ScheduledExecutorService executorService = null;
- private int ccUpload;
-
public void sendCallback(JSONObject obj) {
/* we check the webview has been initialized */
if (ready) {
@@ -158,7 +163,8 @@ private void initManager(String options, final CallbackContext callbackContext)
try {
final JSONObject settings = new JSONObject(options);
- ccUpload = settings.getInt("parallelUploadsLimit");
+ int ccUpload = settings.getInt("parallelUploadsLimit");
+
// Rebuild base HTTP config
httpClientBaseConfig = new Data.Builder()
.putInt(UploadTask.KEY_INPUT_CONFIG_CONCURRENT_DOWNLOADS, ccUpload)
@@ -200,15 +206,6 @@ private void initManager(String options, final CallbackContext callbackContext)
.observeForever((tasks) -> {
int completedTasks = 0;
for (WorkInfo info : tasks) {
- // No db in main thread
- executorService.schedule(() -> {
- final List uploadEventsList = ackDatabase
- .uploadEventDao()
- .getAll();
- for (UploadEvent ack : uploadEventsList) {
- handleAck(ack.getOutputData());
- }
- }, 0, TimeUnit.MILLISECONDS);
switch (info.getState()) {
// If the upload in not finished, publish its progress
case RUNNING:
@@ -216,7 +213,7 @@ private void initManager(String options, final CallbackContext callbackContext)
String id = info.getProgress().getString(UploadTask.KEY_PROGRESS_ID);
int progress = info.getProgress().getInt(UploadTask.KEY_PROGRESS_PERCENT, 0);
- logMessage("initManager: " + info.getId() + " (" + info.getState() + ") Progress: " + progress);
+ Log.d(TAG, "initManager: " + info.getId() + " (" + info.getState() + ") Progress: " + progress);
sendProgress(id, progress);
}
break;
@@ -224,8 +221,16 @@ private void initManager(String options, final CallbackContext callbackContext)
case BLOCKED:
case ENQUEUED:
case SUCCEEDED:
- logMessage("Task succeeded: " + info.getId());
completedTasks++;
+ // No db in main thread
+ executorService.schedule(() -> {
+ // The corresponding ACK is already in the DB, if it not, the task is just a leftover
+ String id = info.getOutputData().getString(UploadTask.KEY_OUTPUT_ID);
+ if (ackDatabase.uploadEventDao().exists(id)) {
+ handleAck(info.getOutputData());
+ }
+ }, 0, TimeUnit.MILLISECONDS);
+ break;
case FAILED:
// The task can't fail completely so something really bad has happened.
logMessage("eventLabel='Uploader failed inexplicably' error='" + info.getOutputData() + "'");
@@ -295,67 +300,59 @@ private void addUpload(JSONObject jsonPayload) {
} catch(PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
-
- AckDatabase.getInstance(cordova.getContext()).pendingUploadDao().insert(
- new PendingUpload(
- uploadId,
- new Data.Builder()
- // Put base info
- .putString(UploadTask.KEY_INPUT_ID, uploadId)
- .putString(UploadTask.KEY_INPUT_URL, (String) payload.get("serverUrl"))
- .putString(UploadTask.KEY_INPUT_FILEPATH, (String) payload.get("filePath"))
- .putString(UploadTask.KEY_INPUT_FILE_KEY, (String) payload.get("fileKey"))
- .putString(UploadTask.KEY_INPUT_HTTP_METHOD, (String) payload.get("requestMethod"))
- // Put headers
- .putInt(UploadTask.KEY_INPUT_HEADERS_COUNT, headersNames.size())
- .putStringArray(UploadTask.KEY_INPUT_HEADERS_NAMES, headersNames.toArray(new String[0]))
- .putAll(headerValues)
- // Put query parameters
- .putInt(UploadTask.KEY_INPUT_PARAMETERS_COUNT, parameterNames.size())
- .putStringArray(UploadTask.KEY_INPUT_PARAMETERS_NAMES, parameterNames.toArray(new String[0]))
- .putAll(parameterValues)
- // Put notification stuff
- .putString(UploadTask.KEY_INPUT_NOTIFICATION_TITLE, (String) payload.get("notificationTitle"))
- .putString(UploadTask.KEY_INPUT_NOTIFICATION_ICON, cordova.getActivity().getPackageName() + ":drawable/ic_upload")
- .putString(UploadTask.KEY_INPUT_CONFIG_INTENT_ACTIVITY, intentActivity)
-
- // Put config stuff
- .putAll(httpClientBaseConfig)
- .build()
- )
+ startUpload(uploadId, new Data.Builder()
+ // Put base info
+ .putString(UploadTask.KEY_INPUT_ID, uploadId)
+ .putString(UploadTask.KEY_INPUT_URL, (String) payload.get("serverUrl"))
+ .putString(UploadTask.KEY_INPUT_FILEPATH, (String) payload.get("filePath"))
+ .putString(UploadTask.KEY_INPUT_FILE_KEY, (String) payload.get("fileKey"))
+ .putString(UploadTask.KEY_INPUT_HTTP_METHOD, (String) payload.get("requestMethod"))
+
+ // Put headers
+ .putInt(UploadTask.KEY_INPUT_HEADERS_COUNT, headersNames.size())
+ .putStringArray(UploadTask.KEY_INPUT_HEADERS_NAMES, headersNames.toArray(new String[0]))
+ .putAll(headerValues)
+
+ // Put query parameters
+ .putInt(UploadTask.KEY_INPUT_PARAMETERS_COUNT, parameterNames.size())
+ .putStringArray(UploadTask.KEY_INPUT_PARAMETERS_NAMES, parameterNames.toArray(new String[0]))
+ .putAll(parameterValues)
+
+ // Put notification stuff
+ .putString(UploadTask.KEY_INPUT_NOTIFICATION_TITLE, (String) payload.get("notificationTitle"))
+ .putString(UploadTask.KEY_INPUT_NOTIFICATION_ICON, cordova.getActivity().getPackageName() + ":drawable/ic_upload")
+ .putString(UploadTask.KEY_INPUT_CONFIG_INTENT_ACTIVITY, intentActivity)
+
+ // Put config stuff
+ .putAll(httpClientBaseConfig)
+ .build()
);
-
- if (!workerIsStarted) {
- startWorkers();
- workerIsStarted = true;
- }
}
- private void startWorkers() {
- logMessage("startUpload: Starting worker via work manager");
-
- for (int i = 0; i < ccUpload; i++) {
- OneTimeWorkRequest.Builder workRequestBuilder = new OneTimeWorkRequest.Builder(UploadTask.class)
- .setConstraints(new Constraints.Builder()
- .setRequiredNetworkType(NetworkType.CONNECTED)
- .build()
- )
- .keepResultsForAtLeast(0, TimeUnit.MILLISECONDS)
- .setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.SECONDS)
- .addTag(FileTransferBackground.WORK_TAG_UPLOAD);
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- workRequestBuilder.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST);
- }
-
- OneTimeWorkRequest workRequest = workRequestBuilder.build();
+ private void startUpload(final String uploadId, final Data payload) {
+ Log.d(TAG, "startUpload: Starting work via work manager");
+
+ OneTimeWorkRequest.Builder workRequestBuilder = new OneTimeWorkRequest.Builder(UploadTask.class)
+ .setConstraints(new Constraints.Builder()
+ .setRequiredNetworkType(NetworkType.CONNECTED)
+ .build()
+ )
+ .keepResultsForAtLeast(0, TimeUnit.MILLISECONDS)
+ .setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.SECONDS)
+ .addTag(FileTransferBackground.WORK_TAG_UPLOAD)
+ .addTag(getCurrentTag(cordova.getContext()))
+ .setInputData(payload);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ workRequestBuilder.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST);
+ }
- WorkManager.getInstance(cordova.getContext())
- .enqueueUniqueWork(FileTransferBackground.WORK_TAG_UPLOAD + "_" + i, ExistingWorkPolicy.KEEP, workRequest);
+ OneTimeWorkRequest workRequest = workRequestBuilder.build();
- logMessage("eventLabel=Uploader starting uploads via worker" + i);
- }
+ WorkManager.getInstance(cordova.getContext())
+ .enqueueUniqueWork(uploadId, ExistingWorkPolicy.APPEND, workRequest);
+ logMessage("eventLabel='Uploader starting upload' uploadId='" + uploadId + "'");
}
private void sendAddingUploadError(String uploadId, Exception error) {
@@ -433,7 +430,6 @@ private void handleAck(final Data ackData) {
*/
private void cleanupUpload(final String uploadId) {
final UploadEvent ack = AckDatabase.getInstance(cordova.getContext()).uploadEventDao().getById(uploadId);
-
// If the upload is done there is an ACK of it, so get file name from there
if (ack != null) {
if (ack.getOutputData().getString(UploadTask.KEY_OUTPUT_RESPONSE_FILE) != null) {
@@ -505,15 +501,44 @@ public static HashMap convertToHashMap(JSONObject jsonObject) th
return hashMap;
}
- public static void logMessage(String message) {
- Log.d("CordovaBackgroundUpload", message);
+ public static String getCurrentTag(Context context) {
+ final long now = System.currentTimeMillis();
+ if (currentTag != null && now - currentTagFetchedAt <= 5000) {
+ return currentTag;
+ }
+ currentTagFetchedAt = now;
+ currentTag = fetchCurrentTag(context);
+ return currentTag;
}
- public static void logMessageInfo(String message) {
- Log.i("CordovaBackgroundUpload", message);
+ public static String fetchCurrentTag(Context context) {
+ WorkQuery workQuery = WorkQuery.Builder
+ .fromTags(Arrays.asList(FileTransferBackground.WORK_TAG_UPLOAD))
+ .addStates(Arrays.asList(WorkInfo.State.RUNNING, WorkInfo.State.ENQUEUED))
+ .build();
+ List workInfo;
+ try {
+ workInfo = WorkManager.getInstance(context)
+ .getWorkInfos(workQuery)
+ .get();
+ } catch (ExecutionException | InterruptedException e) {
+ Log.w(TAG, "getForegroundInfo: Problem while retrieving task list:", e);
+ workInfo = Collections.emptyList();
+ }
+ String prefix = "packet_";
+ for (WorkInfo info : workInfo) {
+ if (!info.getState().isFinished()) {
+ for (String tag : info.getTags()) {
+ if (tag.startsWith(prefix)) {
+ return tag;
+ }
+ }
+ }
+ }
+ return prefix + UUID.randomUUID().toString();
}
- public static void logMessageError(String message, Exception exception) {
- Log.e("CordovaBackgroundUpload", message, exception);
+ public static void logMessage(String message) {
+ Log.d("CordovaBackgroundUpload", message);
}
}
diff --git a/src/android/PendingUpload.java b/src/android/PendingUpload.java
deleted file mode 100644
index a1fe672a..00000000
--- a/src/android/PendingUpload.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.spoon.backgroundfileupload;
-
-import androidx.annotation.NonNull;
-import androidx.room.ColumnInfo;
-import androidx.room.Entity;
-import androidx.room.PrimaryKey;
-import androidx.work.Data;
-
-@Entity(tableName = "pending_upload")
-public class PendingUpload {
- @PrimaryKey
- @NonNull
- private String id;
-
- @ColumnInfo(name = "output_data")
- @NonNull
- private Data inputData;
-
- @ColumnInfo(name = "state")
- @NonNull
- private String state;
-
- public PendingUpload(@NonNull final String id, @NonNull final Data inputData) {
- this.id = id;
- this.inputData = inputData;
- this.state = "PENDING";
- }
-
- @NonNull
- public void setState(@NonNull final String state) {
- this.state = state;
- }
-
- @NonNull
- public String getId() {
- return id;
- }
-
- @NonNull
- public Data getInputData() {
- return inputData;
- }
-
- @NonNull
- public String getState() {
- return state;
- }
-}
diff --git a/src/android/PendingUploadDao.java b/src/android/PendingUploadDao.java
deleted file mode 100644
index a1450265..00000000
--- a/src/android/PendingUploadDao.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package com.spoon.backgroundfileupload;
-
-import androidx.room.Dao;
-import androidx.room.Delete;
-import androidx.room.Insert;
-import androidx.room.OnConflictStrategy;
-import androidx.room.Query;
-
-import java.util.List;
-
-@Dao
-public interface PendingUploadDao {
- @Query("SELECT * FROM pending_upload")
- List getAll();
-
- @Query("SELECT COUNT(*) FROM pending_upload")
- int getAllCount();
-
- @Query("SELECT * FROM pending_upload WHERE id = :id")
- PendingUpload getById(final String id);
-
- @Query("SELECT COUNT(id) FROM pending_upload WHERE id = :id")
- int getCountById(final String id);
-
- @Query("SELECT * FROM pending_upload WHERE state = 'PENDING' LIMIT 1")
- PendingUpload getFirstPendingEntry();
-
- @Query("SELECT COUNT(*) FROM pending_upload WHERE state = 'PENDING'")
- int getPendingUploadsCount();
-
- @Query("SELECT * FROM pending_upload WHERE state = 'UPLOADED'")
- List getCompletedUploads();
-
- @Query("SELECT COUNT(*) FROM pending_upload WHERE state = 'UPLOADED'")
- int getCompletedUploadsCount();
-
- @Query("UPDATE pending_upload SET state = 'PENDING' WHERE ID = :id")
- void markAsPending(final String id);
-
- @Query("UPDATE pending_upload SET state = 'UPLOADED' WHERE ID = :id")
- void markAsUploaded(final String id);
-
- default boolean exists(final String id) {
- return getCountById(id) > 0;
- }
-
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- void insert(final PendingUpload ack);
-
- @Delete
- void delete(final PendingUpload ack);
-
- default void delete(final String id) {
- PendingUpload pendingUpload = getById(id);
- if (pendingUpload != null) {
- delete(pendingUpload);
- }
- }
-}
diff --git a/src/android/UploadEventDao.java b/src/android/UploadEventDao.java
index 2249242a..fade69d9 100644
--- a/src/android/UploadEventDao.java
+++ b/src/android/UploadEventDao.java
@@ -13,17 +13,11 @@ public interface UploadEventDao {
@Query("SELECT * FROM upload_event")
List getAll();
- @Query("SELECT COUNT(*) FROM upload_event")
- int getAllCount();
-
@Query("SELECT * FROM upload_event WHERE id = :id")
UploadEvent getById(final String id);
- @Query("SELECT COUNT(id) FROM upload_event WHERE id = :id")
- int getCountById(final String id);
-
default boolean exists(final String id) {
- return getCountById(id) > 0;
+ return getById(id) != null;
}
@Insert(onConflict = OnConflictStrategy.REPLACE)
diff --git a/src/android/UploadForegroundNotification.java b/src/android/UploadForegroundNotification.java
index 386e29c8..42e77ff0 100644
--- a/src/android/UploadForegroundNotification.java
+++ b/src/android/UploadForegroundNotification.java
@@ -6,16 +6,21 @@
import android.content.Intent;
import android.graphics.Color;
import android.os.Build;
+import android.util.Log;
import androidx.annotation.IntegerRes;
import androidx.core.app.NotificationCompat;
import androidx.work.ForegroundInfo;
+import androidx.work.WorkInfo;
+import androidx.work.WorkManager;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong;
public class UploadForegroundNotification {
@@ -56,9 +61,34 @@ static ForegroundInfo getForegroundInfo(final Context context) {
return cachedInfo;
}
- float totalProgressStore = (float) (AckDatabase.getInstance(context).pendingUploadDao().getCompletedUploadsCount() / (double) AckDatabase.getInstance(context).pendingUploadDao().getAllCount());
+ List workInfo;
+ try {
+ workInfo = WorkManager.getInstance(context)
+ .getWorkInfosByTag(FileTransferBackground.getCurrentTag(context))
+ .get();
+ } catch (ExecutionException | InterruptedException e) {
+ Log.w(UploadTask.TAG, "getForegroundInfo: Problem while retrieving task list:", e);
+ workInfo = Collections.emptyList();
+ }
+
+ float uploadingProgress = 0f;
+ int uploadDone = 0;
+ int uploadCount = 0;
+ for (WorkInfo info : workInfo) {
+ if (!info.getState().isFinished()) {
+ final Float progress = collectiveProgress.get(info.getId());
+ if (progress != null) {
+ uploadingProgress += progress;
+ }
+ } else {
+ uploadDone++;
+ }
+ uploadCount++;
+ }
+
+ float totalProgressStore = ((float) uploadDone) / uploadCount;
- FileTransferBackground.logMessage("eventLabel='getForegroundInfo: general (" + totalProgressStore + ") all (" + collectiveProgress + ")'");
+ Log.d(UploadTask.TAG, "eventLabel='getForegroundInfo: general (" + uploadingProgress + ") all (" + collectiveProgress + ")'");
Class> mainActivityClass = null;
try {
diff --git a/src/android/UploadNotification.java b/src/android/UploadNotification.java
index 2d2fe2d1..092ce707 100644
--- a/src/android/UploadNotification.java
+++ b/src/android/UploadNotification.java
@@ -9,12 +9,18 @@
import android.content.Intent;
import android.graphics.Color;
import android.os.Build;
+import android.util.Log;
import androidx.annotation.IntegerRes;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
+import androidx.work.WorkInfo;
+import androidx.work.WorkManager;
+import java.util.Collections;
+import java.util.List;
import java.util.Random;
+import java.util.concurrent.ExecutionException;
public class UploadNotification {
private Context context;
@@ -42,7 +48,26 @@ public class UploadNotification {
}
public void updateProgress() {
- float totalProgressStore = (float) (AckDatabase.getInstance(context).pendingUploadDao().getCompletedUploadsCount() / (double) AckDatabase.getInstance(context).pendingUploadDao().getAllCount());
+ List workInfo;
+ try {
+ workInfo = WorkManager.getInstance(context)
+ .getWorkInfosByTag(FileTransferBackground.getCurrentTag(context))
+ .get();
+ } catch (ExecutionException | InterruptedException e) {
+ Log.w(UploadTask.TAG, "getForegroundInfo: Problem while retrieving task list:", e);
+ workInfo = Collections.emptyList();
+ }
+
+ int uploadDone = 0;
+ int uploadCount = 0;
+ for (WorkInfo info : workInfo) {
+ if (info.getState().isFinished()) {
+ uploadDone++;
+ }
+ uploadCount++;
+ }
+
+ float totalProgressStore = ((float) uploadDone) / uploadCount;
notificationBuilder.setProgress(100, (int) (totalProgressStore * 100f), false);
notificationManager.notify(UploadNotification.notificationId, notificationBuilder.build());
}
diff --git a/src/android/UploadTask.java b/src/android/UploadTask.java
index f8bbf766..919f6e9f 100644
--- a/src/android/UploadTask.java
+++ b/src/android/UploadTask.java
@@ -3,6 +3,7 @@
import android.content.Context;
import android.net.ConnectivityManager;
import android.os.Build;
+import android.util.Log;
import android.webkit.MimeTypeMap;
import androidx.annotation.NonNull;
@@ -19,8 +20,8 @@
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.nio.charset.StandardCharsets;
-import java.util.List;
import java.util.Objects;
+import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLException;
@@ -38,6 +39,8 @@ public final class UploadTask extends Worker {
private static final boolean DEBUG_SKIP_UPLOAD = false;
public static final long DELAY_BETWEEN_NOTIFICATION_UPDATE_MS = 200;
+ public static final String TAG = "CordovaBackgroundUpload";
+
public static final String NOTIFICATION_CHANNEL_ID = "com.spoon.backgroundfileupload.channel";
public static final String NOTIFICATION_CHANNEL_NAME = "upload channel";
@@ -81,28 +84,38 @@ public final class UploadTask extends Worker {
private static UploadNotification uploadNotification = null;
private static UploadForegroundNotification uploadForegroundNotification = null;
+ public static class Mutex {
+ public void acquire() throws InterruptedException { }
+ public void release() { }
+ }
+
private static OkHttpClient httpClient;
private Call currentCall;
- private PendingUpload nextPendingUpload;
-
- private static boolean firstMigrationFlag = false;
+ private static int concurrency = 1;
+ private static Semaphore concurrentUploads = new Semaphore(concurrency, true);
+ private static Mutex concurrencyLock = new Mutex();
public UploadTask(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
- // Migrating code from 4.0.9 to 4.0.10 - Check if upload comes from another worker and does not exists in table
- String oldUploadTaskId = workerParams.getInputData().getString(KEY_INPUT_ID);
- if (!firstMigrationFlag && oldUploadTaskId != null && AckDatabase.getInstance(getApplicationContext()).pendingUploadDao().getById(oldUploadTaskId) == null) {
- FileTransferBackground.logMessage("Migrating upload " + oldUploadTaskId);
- AckDatabase.getInstance(getApplicationContext()).pendingUploadDao().insert(new PendingUpload(oldUploadTaskId, workerParams.getInputData()));
- FileTransferBackground.logMessage("Retrying migrated upload " + oldUploadTaskId + " after some seconds...");
- firstMigrationFlag = true;
- }
+ int concurrencyConfig = workerParams.getInputData().getInt(KEY_INPUT_CONFIG_CONCURRENT_DOWNLOADS, 1);
- nextPendingUpload = AckDatabase.getInstance(getApplicationContext()).pendingUploadDao().getFirstPendingEntry();
+ try {
+ concurrencyLock.acquire();
+ try {
+ if (concurrency != concurrencyConfig) {
+ concurrency = concurrencyConfig;
+ concurrentUploads = new Semaphore(concurrencyConfig, true);
+ }
+ } finally {
+ concurrencyLock.release();
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
if (httpClient == null) {
httpClient = new OkHttpClient.Builder()
@@ -116,20 +129,20 @@ public UploadTask(@NonNull Context context, @NonNull WorkerParameters workerPara
.build();
}
- httpClient.dispatcher().setMaxRequests(nextPendingUpload.getInputData().getInt(KEY_INPUT_CONFIG_CONCURRENT_DOWNLOADS, 2));
+ httpClient.dispatcher().setMaxRequests(workerParams.getInputData().getInt(KEY_INPUT_CONFIG_CONCURRENT_DOWNLOADS, 2));
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
UploadForegroundNotification.configure(
- nextPendingUpload.getInputData().getString(UploadTask.KEY_INPUT_NOTIFICATION_TITLE),
- getApplicationContext().getResources().getIdentifier(nextPendingUpload.getInputData().getString(KEY_INPUT_NOTIFICATION_ICON), null, null),
- nextPendingUpload.getInputData().getString(UploadTask.KEY_INPUT_CONFIG_INTENT_ACTIVITY)
+ workerParams.getInputData().getString(UploadTask.KEY_INPUT_NOTIFICATION_TITLE),
+ getApplicationContext().getResources().getIdentifier(workerParams.getInputData().getString(KEY_INPUT_NOTIFICATION_ICON), null, null),
+ workerParams.getInputData().getString(UploadTask.KEY_INPUT_CONFIG_INTENT_ACTIVITY)
);
uploadForegroundNotification = new UploadForegroundNotification();
} else {
UploadNotification.configure(
- nextPendingUpload.getInputData().getString(UploadTask.KEY_INPUT_NOTIFICATION_TITLE),
- getApplicationContext().getResources().getIdentifier(nextPendingUpload.getInputData().getString(KEY_INPUT_NOTIFICATION_ICON), null, null),
- nextPendingUpload.getInputData().getString(UploadTask.KEY_INPUT_CONFIG_INTENT_ACTIVITY)
+ workerParams.getInputData().getString(UploadTask.KEY_INPUT_NOTIFICATION_TITLE),
+ getApplicationContext().getResources().getIdentifier(workerParams.getInputData().getString(KEY_INPUT_NOTIFICATION_ICON), null, null),
+ workerParams.getInputData().getString(UploadTask.KEY_INPUT_CONFIG_INTENT_ACTIVITY)
);
uploadNotification = new UploadNotification(getApplicationContext());
}
@@ -142,152 +155,134 @@ public Result doWork() {
return Result.retry();
}
- // Migrating code from 4.0.9 to 4.0.10 - Check if upload comes from another worker and does not exists in table
- String oldUploadTaskId = getInputData().getString(KEY_INPUT_ID);
- if (oldUploadTaskId != null && AckDatabase.getInstance(getApplicationContext()).pendingUploadDao().getById(oldUploadTaskId) == null && firstMigrationFlag == true) {
- FileTransferBackground.logMessage("Migrating upload " + oldUploadTaskId);
- AckDatabase.getInstance(getApplicationContext()).pendingUploadDao().insert(new PendingUpload(oldUploadTaskId, getInputData()));
- FileTransferBackground.logMessage("Retrying migrated upload " + oldUploadTaskId + " after some seconds...");
- return Result.success();
- }
+ final String id = getInputData().getString(KEY_INPUT_ID);
- do {
- nextPendingUpload = AckDatabase.getInstance(getApplicationContext()).pendingUploadDao().getFirstPendingEntry();
-
- final String id = nextPendingUpload.getInputData().getString(KEY_INPUT_ID);
-
- if (id == null) {
- FileTransferBackground.logMessageError("doWork: ID is invalid !", null);
- AckDatabase.getInstance(getApplicationContext()).pendingUploadDao().markAsPending(nextPendingUpload.getId());
- return Result.failure();
- }
+ if (id == null) {
+ Log.e(TAG, "doWork: ID is invalid !");
+ return Result.failure();
+ }
- // Check retry count
- if (getRunAttemptCount() > MAX_TRIES) {
- return Result.success(new Data.Builder()
- .putString(KEY_OUTPUT_ID, id)
- .putBoolean(KEY_OUTPUT_IS_ERROR, true)
- .putString(KEY_OUTPUT_FAILURE_REASON, "Too many retries")
- .putBoolean(KEY_OUTPUT_FAILURE_CANCELED, false)
- .build()
- );
- }
+ // Check retry count
+ if (getRunAttemptCount() > MAX_TRIES) {
+ return Result.success(new Data.Builder()
+ .putString(KEY_OUTPUT_ID, id)
+ .putBoolean(KEY_OUTPUT_IS_ERROR, true)
+ .putString(KEY_OUTPUT_FAILURE_REASON, "Too many retries")
+ .putBoolean(KEY_OUTPUT_FAILURE_CANCELED, false)
+ .build()
+ );
+ }
- Request request = null;
- try {
- request = createRequest();
- } catch (FileNotFoundException e) {
- FileTransferBackground.logMessageError("doWork: File not found !", e);
- return Result.success(new Data.Builder()
- .putString(KEY_OUTPUT_ID, id)
- .putBoolean(KEY_OUTPUT_IS_ERROR, true)
- .putString(KEY_OUTPUT_FAILURE_REASON, "File not found !")
- .putBoolean(KEY_OUTPUT_FAILURE_CANCELED, false)
- .build()
- );
- } catch (NullPointerException e) {
- return Result.retry();
- }
+ Request request = null;
+ try {
+ request = createRequest();
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "doWork: File not found !", e);
+ return Result.success(new Data.Builder()
+ .putString(KEY_OUTPUT_ID, id)
+ .putBoolean(KEY_OUTPUT_IS_ERROR, true)
+ .putString(KEY_OUTPUT_FAILURE_REASON, "File not found !")
+ .putBoolean(KEY_OUTPUT_FAILURE_CANCELED, false)
+ .build()
+ );
+ } catch (NullPointerException e) {
+ return Result.retry();
+ }
- // Register me
- uploadForegroundNotification.progress(getId(), 0f);
- handleNotification();
+ // Register me
+ uploadForegroundNotification.progress(getId(), 0f);
+ handleNotification();
- // Start call
- currentCall = httpClient.newCall(request);
+ // Start call
+ currentCall = httpClient.newCall(request);
- // Block until call is finished (or cancelled)
- Response response = null;
- try {
- if (!DEBUG_SKIP_UPLOAD) {
+ // Block until call is finished (or cancelled)
+ Response response = null;
+ try {
+ if (!DEBUG_SKIP_UPLOAD) {
+ try {
try {
+ concurrentUploads.acquire();
try {
response = currentCall.execute();
} catch (SocketTimeoutException e) {
return Result.retry();
+ } finally {
+ concurrentUploads.release();
}
- } catch (SocketException | ProtocolException | SSLException e) {
- currentCall.cancel();
+ } catch (InterruptedException e) {
return Result.retry();
}
- } else {
- for (int i = 0; i < 10; i++) {
- handleProgress(i * 100, 1000);
- // Can be interrupted
- Thread.sleep(200);
- if (isStopped()) {
- throw new InterruptedException("Stopped");
- }
- }
- }
- } catch (IOException | InterruptedException e) {
- // If it was user cancelled its ok
- // See #handleProgress for cancel code
- if (isStopped()) {
- final Data data = new Data.Builder()
- .putString(KEY_OUTPUT_ID, id)
- .putBoolean(KEY_OUTPUT_IS_ERROR, true)
- .putString(KEY_OUTPUT_FAILURE_REASON, "User cancelled")
- .putBoolean(KEY_OUTPUT_FAILURE_CANCELED, true)
- .build();
- AckDatabase.getInstance(getApplicationContext()).pendingUploadDao().markAsUploaded(nextPendingUpload.getId());
- AckDatabase.getInstance(getApplicationContext()).uploadEventDao().insert(new UploadEvent(id, data));
- return Result.success(data);
- } else {
- // But if it was not it must be a connectivity problem or
- // something similar so we retry later
- FileTransferBackground.logMessageError("doWork: Call failed, retrying later", e);
+ } catch (SocketException | ProtocolException | SSLException e) {
+ currentCall.cancel();
return Result.retry();
}
- } finally {
- // Always remove ourselves from the notification
- uploadForegroundNotification.done(getId());
- }
-
- // Start building the output data
- final Data.Builder outputData = new Data.Builder()
- .putString(KEY_OUTPUT_ID, id)
- .putBoolean(KEY_OUTPUT_IS_ERROR, false)
- .putInt(KEY_OUTPUT_STATUS_CODE, (!DEBUG_SKIP_UPLOAD) ? response.code() : 200);
-
- // Try read the response body, if any
- try {
- final String res;
- if (!DEBUG_SKIP_UPLOAD) {
- res = response.body() != null ? response.body().string() : "";
- } else {
- res = "heyo";
- }
- final String filename = "upload-response-" + getId() + ".cached-response";
-
- try (FileOutputStream fos = getApplicationContext().openFileOutput(filename, Context.MODE_PRIVATE)) {
- fos.write(res.getBytes(StandardCharsets.UTF_8));
+ } else {
+ for (int i = 0; i < 10; i++) {
+ handleProgress(i * 100, 1000);
+ // Can be interrupted
+ Thread.sleep(200);
+ if (isStopped()) {
+ throw new InterruptedException("Stopped");
+ }
}
+ }
+ } catch (IOException | InterruptedException e) {
+ // If it was user cancelled its ok
+ // See #handleProgress for cancel code
+ if (isStopped()) {
+ final Data data = new Data.Builder()
+ .putString(KEY_OUTPUT_ID, id)
+ .putBoolean(KEY_OUTPUT_IS_ERROR, true)
+ .putString(KEY_OUTPUT_FAILURE_REASON, "User cancelled")
+ .putBoolean(KEY_OUTPUT_FAILURE_CANCELED, true)
+ .build();
+ AckDatabase.getInstance(getApplicationContext()).uploadEventDao().insert(new UploadEvent(id, data));
+ return Result.success(data);
+ } else {
+ // But if it was not it must be a connectivity problem or
+ // something similar so we retry later
+ Log.e(TAG, "doWork: Call failed, retrying later", e);
+ return Result.retry();
+ }
+ } finally {
+ // Always remove ourselves from the notification
+ uploadForegroundNotification.done(getId());
+ }
- outputData.putString(KEY_OUTPUT_RESPONSE_FILE, filename);
-
- } catch (IOException e) {
- // Should never happen, but if it does it has something to do with reading the response
- FileTransferBackground.logMessageError("doWork: Error while reading the response body", e);
+ // Start building the output data
+ final Data.Builder outputData = new Data.Builder()
+ .putString(KEY_OUTPUT_ID, id)
+ .putBoolean(KEY_OUTPUT_IS_ERROR, false)
+ .putInt(KEY_OUTPUT_STATUS_CODE, (!DEBUG_SKIP_UPLOAD) ? response.code() : 200);
+
+ // Try read the response body, if any
+ try {
+ final String res;
+ if (!DEBUG_SKIP_UPLOAD) {
+ res = response.body() != null ? response.body().string() : "";
+ } else {
+ res = "heyo";
+ }
+ final String filename = "upload-response-" + getId() + ".cached-response";
- // But recover and replace the body with something else
- outputData.putString(KEY_OUTPUT_RESPONSE_FILE, null);
+ try (FileOutputStream fos = getApplicationContext().openFileOutput(filename, Context.MODE_PRIVATE)) {
+ fos.write(res.getBytes(StandardCharsets.UTF_8));
}
- final Data data = outputData.build();
- AckDatabase.getInstance(getApplicationContext()).pendingUploadDao().markAsUploaded(nextPendingUpload.getId());
- AckDatabase.getInstance(getApplicationContext()).uploadEventDao().insert(new UploadEvent(id, data));
- } while(AckDatabase.getInstance(getApplicationContext()).pendingUploadDao().getPendingUploadsCount() > 0);
+ outputData.putString(KEY_OUTPUT_RESPONSE_FILE, filename);
- final List pendingUploads = AckDatabase.getInstance(getApplicationContext()).pendingUploadDao().getCompletedUploads();
+ } catch (IOException e) {
+ // Should never happen, but if it does it has something to do with reading the response
+ Log.e(TAG, "doWork: Error while reading the response body", e);
- for (PendingUpload pendingUpload: pendingUploads) {
- AckDatabase.getInstance(getApplicationContext()).pendingUploadDao().delete(pendingUpload);
+ // But recover and replace the body with something else
+ outputData.putString(KEY_OUTPUT_RESPONSE_FILE, null);
}
- FileTransferBackground.workerIsStarted = false;
-
- return Result.success();
+ final Data data = outputData.build();
+ AckDatabase.getInstance(getApplicationContext()).uploadEventDao().insert(new UploadEvent(id, data));
+ return Result.success(data);
}
/**
@@ -304,13 +299,13 @@ private void handleProgress(long bytesWritten, long totalBytes) {
float percent = (float) bytesWritten / (float) totalBytes;
UploadForegroundNotification.progress(getId(), percent);
- FileTransferBackground.logMessageInfo("handleProgress: " + getId() + " Progress: " + (int) (percent * 100f));
+ Log.i(TAG, "handleProgress: " + getId() + " Progress: " + (int) (percent * 100f));
final Data data = new Data.Builder()
- .putString(KEY_PROGRESS_ID, nextPendingUpload.getInputData().getString(KEY_INPUT_ID))
+ .putString(KEY_PROGRESS_ID, getInputData().getString(KEY_INPUT_ID))
.putInt(KEY_PROGRESS_PERCENT, (int) (percent * 100f))
.build();
- FileTransferBackground.logMessage("handleProgress: Progress data: " + data);
+ Log.d(TAG, "handleProgress: Progress data: " + data);
setProgressAsync(data);
handleNotification();
}
@@ -323,13 +318,13 @@ private void handleProgress(long bytesWritten, long totalBytes) {
*/
@NonNull
private Request createRequest() throws FileNotFoundException {
- final String filepath = nextPendingUpload.getInputData().getString(KEY_INPUT_FILEPATH);
+ final String filepath = getInputData().getString(KEY_INPUT_FILEPATH);
assert filepath != null;
- final String fileKey = nextPendingUpload.getInputData().getString(KEY_INPUT_FILE_KEY);
+ final String fileKey = getInputData().getString(KEY_INPUT_FILE_KEY);
assert fileKey != null;
// Build URL
- HttpUrl url = Objects.requireNonNull(HttpUrl.parse(nextPendingUpload.getInputData().getString(KEY_INPUT_URL))).newBuilder().build();
+ HttpUrl url = Objects.requireNonNull(HttpUrl.parse(getInputData().getString(KEY_INPUT_URL))).newBuilder().build();
// Build file reader
String extension = MimeTypeMap.getFileExtensionFromUrl(filepath);
@@ -348,14 +343,14 @@ private Request createRequest() throws FileNotFoundException {
final MultipartBody.Builder bodyBuilder = new MultipartBody.Builder();
// With the parameters
- final int parametersCount = nextPendingUpload.getInputData().getInt(KEY_INPUT_PARAMETERS_COUNT, 0);
+ final int parametersCount = getInputData().getInt(KEY_INPUT_PARAMETERS_COUNT, 0);
if (parametersCount > 0) {
- final String[] parameterNames = nextPendingUpload.getInputData().getStringArray(KEY_INPUT_PARAMETERS_NAMES);
+ final String[] parameterNames = getInputData().getStringArray(KEY_INPUT_PARAMETERS_NAMES);
assert parameterNames != null;
for (int i = 0; i < parametersCount; i++) {
final String key = parameterNames[i];
- final Object value = nextPendingUpload.getInputData().getKeyValueMap().get(KEY_INPUT_PARAMETER_VALUE_PREFIX + i);
+ final Object value = getInputData().getKeyValueMap().get(KEY_INPUT_PARAMETER_VALUE_PREFIX + i);
bodyBuilder.addFormDataPart(key, value.toString());
}
@@ -365,7 +360,7 @@ private Request createRequest() throws FileNotFoundException {
bodyBuilder.setType(MultipartBody.FORM);
// Start build request
- String method = nextPendingUpload.getInputData().getString(KEY_INPUT_HTTP_METHOD);
+ String method = getInputData().getString(KEY_INPUT_HTTP_METHOD);
if (method == null) {
method = "POST";
}
@@ -374,12 +369,12 @@ private Request createRequest() throws FileNotFoundException {
.method(method.toUpperCase(), bodyBuilder.build());
// Write headers
- final int headersCount = nextPendingUpload.getInputData().getInt(KEY_INPUT_HEADERS_COUNT, 0);
- final String[] headerNames = nextPendingUpload.getInputData().getStringArray(KEY_INPUT_HEADERS_NAMES);
+ final int headersCount = getInputData().getInt(KEY_INPUT_HEADERS_COUNT, 0);
+ final String[] headerNames = getInputData().getStringArray(KEY_INPUT_HEADERS_NAMES);
assert headerNames != null;
for (int i = 0; i < headersCount; i++) {
final String key = headerNames[i];
- final Object value = nextPendingUpload.getInputData().getKeyValueMap().get(KEY_INPUT_HEADER_VALUE_PREFIX + i);
+ final Object value = getInputData().getKeyValueMap().get(KEY_INPUT_HEADER_VALUE_PREFIX + i);
requestBuilder.addHeader(key, value.toString());
}
@@ -389,19 +384,19 @@ private Request createRequest() throws FileNotFoundException {
}
private void handleNotification() {
- FileTransferBackground.logMessage("Upload Notification");
+ Log.d(TAG, "Upload Notification");
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
setForegroundAsync(uploadForegroundNotification.getForegroundInfo(getApplicationContext()));
} else {
uploadNotification.updateProgress();
}
- FileTransferBackground.logMessage("Upload Notification Exit");
+ Log.d(TAG, "Upload Notification Exit");
}
private synchronized boolean hasNetworkConnection() {
ConnectivityManager connectivityManager = (ConnectivityManager) getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
if((connectivityManager == null) || (connectivityManager.getActiveNetworkInfo() == null) || (connectivityManager.getActiveNetworkInfo().isConnectedOrConnecting() == false)) {
- FileTransferBackground.logMessage("No internet connection");
+ Log.d(TAG, "No internet connection");
return false;
}
return true;
From 7716bd9213b19fea77c38701b80e788b6ba8699d Mon Sep 17 00:00:00 2001
From: Dinitri Ragoo <42667690+dinitri@users.noreply.github.com>
Date: Wed, 18 Oct 2023 19:32:00 +0400
Subject: [PATCH 2/6] update version for migration
---
src/android/AckDatabase.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/android/AckDatabase.java b/src/android/AckDatabase.java
index f528b276..ac4cc3ed 100644
--- a/src/android/AckDatabase.java
+++ b/src/android/AckDatabase.java
@@ -8,7 +8,7 @@
import androidx.room.TypeConverters;
import androidx.work.Data;
-@Database(entities = {UploadEvent.class}, version = 3)
+@Database(entities = {UploadEvent.class}, version = 5)
@TypeConverters(value = {Data.class})
public abstract class AckDatabase extends RoomDatabase {
private static AckDatabase instance;
From ae783df675be359af6fc58f35730e174a8e612cd Mon Sep 17 00:00:00 2001
From: Dinitri Ragoo <42667690+dinitri@users.noreply.github.com>
Date: Thu, 22 Feb 2024 11:18:37 +0400
Subject: [PATCH 3/6] update plugin cordova plugin file v8
---
plugin.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/plugin.xml b/plugin.xml
index 6b68bed6..01471d34 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -11,7 +11,7 @@
-
+
From 51b955069799055115bc1fe5067237b73d7b520f Mon Sep 17 00:00:00 2001
From: HashirRajah
Date: Thu, 25 Apr 2024 12:48:53 +0400
Subject: [PATCH 4/6] chenaged cordova-plugin-file version
---
plugin.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/plugin.xml b/plugin.xml
index 01471d34..16bf2ece 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -11,7 +11,7 @@
-
+
From 1bab124e7bea2af38cef2241e2a3988f27f982d6 Mon Sep 17 00:00:00 2001
From: HashirRajah
Date: Thu, 25 Apr 2024 14:53:44 +0400
Subject: [PATCH 5/6] use cordova-plugin-file version 6.0.2
---
plugin.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/plugin.xml b/plugin.xml
index 16bf2ece..6b68bed6 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -11,7 +11,7 @@
-
+
From e29f05bbc499cb0e8b2927095caa2d6bd66a7779 Mon Sep 17 00:00:00 2001
From: dinitri ragoo
Date: Thu, 25 Apr 2024 16:59:50 +0400
Subject: [PATCH 6/6] remove framework tag
---
plugin.xml | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/plugin.xml b/plugin.xml
index 6b68bed6..5a9186c2 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -53,7 +53,14 @@
-
+
+
+
+
+
+
+
+