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 @@ - + + + + + + + +