diff --git a/opensrp-core/build.gradle b/opensrp-core/build.gradle index 449da8e07e..9a18111967 100644 --- a/opensrp-core/build.gradle +++ b/opensrp-core/build.gradle @@ -237,6 +237,14 @@ dependencies { compileOnly 'com.google.firebase:firebase-perf' // Add the dependency for the Performance Monitoring library + // WorkManager + def work_version = "2.7.1" + implementation "androidx.work:work-runtime:$work_version" + implementation "androidx.work:work-gcm:$work_version" + implementation "androidx.work:work-multiprocess:$work_version" + implementation "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava" + testImplementation "androidx.work:work-testing:$work_version" + //Mockito def mockitoVersion = '4.6.1' testImplementation("org.mockito:mockito-core:$mockitoVersion") diff --git a/opensrp-core/src/main/java/org/smartregister/sync/RequestParamsBuilder.java b/opensrp-core/src/main/java/org/smartregister/sync/RequestParamsBuilder.java new file mode 100644 index 0000000000..ed04273e9e --- /dev/null +++ b/opensrp-core/src/main/java/org/smartregister/sync/RequestParamsBuilder.java @@ -0,0 +1,90 @@ +package org.smartregister.sync; + +import org.json.JSONException; +import org.json.JSONObject; +import org.smartregister.AllConstants; +import org.smartregister.CoreLibrary; +import org.smartregister.sync.intent.BaseSyncIntentService; + +import java.util.LinkedHashMap; +import java.util.Map; + +import timber.log.Timber; + +/** + * A helper class for building the url request for intent services + */ +public class RequestParamsBuilder { + + private final Map paramMap; + private final StringBuilder getSyncParamsBuilder; + private final JSONObject postSyncParamsBuilder; + + public RequestParamsBuilder() { + this.paramMap = new LinkedHashMap<>(); + this.getSyncParamsBuilder = new StringBuilder(); + this.postSyncParamsBuilder = new JSONObject(); + } + + public RequestParamsBuilder addServerVersion(long value) { + paramMap.put(AllConstants.SERVER_VERSION, value); + return this; + } + + public RequestParamsBuilder addEventPullLimit(int value) { + paramMap.put(AllConstants.LIMIT, value); + return this; + } + + public RequestParamsBuilder configureSyncFilter(String syncFilterParam, String syncFilterValue) { + paramMap.put(syncFilterParam, syncFilterValue); + return this; + } + + public RequestParamsBuilder returnCount(boolean value) { + paramMap.put(AllConstants.RETURN_COUNT, value); + return this; + } + + public RequestParamsBuilder addParam(String key, Object value) { + paramMap.put(key, value); + return this; + } + + public RequestParamsBuilder removeParam(String key) { + paramMap.remove(key); + return this; + } + + public String build() { + + for (Map.Entry entry : paramMap.entrySet()) { + + if (CoreLibrary.getInstance().getSyncConfiguration().isSyncUsingPost()) { + + try { + postSyncParamsBuilder.put(entry.getKey(), entry.getValue()); + } catch (JSONException e) { + Timber.e(e); + } + + } else { + + if (0 != getSyncParamsBuilder.length()) { + getSyncParamsBuilder.append('&'); + } + + getSyncParamsBuilder.append(entry.getKey()).append('=').append(entry.getValue()); + } + + } + + return CoreLibrary.getInstance().getSyncConfiguration().isSyncUsingPost() ? postSyncParamsBuilder.toString() : getSyncParamsBuilder.toString(); + } + + @Override + public String toString() { + return build(); + } + +} diff --git a/opensrp-core/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java b/opensrp-core/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java index db355f2088..769719dcee 100644 --- a/opensrp-core/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java +++ b/opensrp-core/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java @@ -15,7 +15,7 @@ import org.smartregister.domain.SyncStatus; import org.smartregister.repository.AllSharedPreferences; import org.smartregister.service.HTTPAgent; -import org.smartregister.sync.intent.BaseSyncIntentService; +import org.smartregister.sync.RequestParamsBuilder; import org.smartregister.sync.intent.SettingsSyncIntentService; import org.smartregister.util.JsonFormUtils; import org.smartregister.util.Utils; @@ -95,7 +95,7 @@ private void getExtraSettings(JSONArray settings, String accessToken) throws JSO JSONArray completeExtraSettings = new JSONArray(); if (getInstance().getSyncConfiguration() != null && getInstance().getSyncConfiguration().hasExtraSettingsSync()) { String syncParams = getInstance().getSyncConfiguration().getExtraStringSettingsParameters(); - BaseSyncIntentService.RequestParamsBuilder builder = new BaseSyncIntentService.RequestParamsBuilder().addParam(AllConstants.SERVER_VERSION, "0").addParam(AllConstants.RESOLVE, getInstance().getSyncConfiguration().resolveSettings()); + RequestParamsBuilder builder = new RequestParamsBuilder().addParam(AllConstants.SERVER_VERSION, "0").addParam(AllConstants.RESOLVE, getInstance().getSyncConfiguration().resolveSettings()); String url = SettingsSyncIntentService.SETTINGS_URL + "?" + syncParams + "&" + builder.toString(); JSONArray extraSettings = pullSettings(url, accessToken); if (extraSettings != null) { diff --git a/opensrp-core/src/main/java/org/smartregister/sync/intent/BaseSyncIntentService.java b/opensrp-core/src/main/java/org/smartregister/sync/intent/BaseSyncIntentService.java index dcedb1bedc..3a8694570c 100644 --- a/opensrp-core/src/main/java/org/smartregister/sync/intent/BaseSyncIntentService.java +++ b/opensrp-core/src/main/java/org/smartregister/sync/intent/BaseSyncIntentService.java @@ -17,6 +17,8 @@ /** * Created by Vincent Karuri on 26/08/2019 */ + +@Deprecated public class BaseSyncIntentService extends IntentService { public BaseSyncIntentService(String name) { @@ -31,81 +33,4 @@ protected void onHandleIntent(Intent intent) { httpAgent.setReadTimeout(coreLibrary.getSyncConfiguration().getReadTimeout()); } - /** - * A helper class for building the url request for intent services - */ - public static class RequestParamsBuilder { - - private final Map paramMap; - private final StringBuilder getSyncParamsBuilder; - private final JSONObject postSyncParamsBuilder; - - public RequestParamsBuilder() { - this.paramMap = new LinkedHashMap<>(); - this.getSyncParamsBuilder = new StringBuilder(); - this.postSyncParamsBuilder = new JSONObject(); - } - - public RequestParamsBuilder addServerVersion(long value) { - paramMap.put(AllConstants.SERVER_VERSION, value); - return this; - } - - public RequestParamsBuilder addEventPullLimit(int value) { - paramMap.put(AllConstants.LIMIT, value); - return this; - } - - public RequestParamsBuilder configureSyncFilter(String syncFilterParam, String syncFilterValue) { - paramMap.put(syncFilterParam, syncFilterValue); - return this; - } - - public RequestParamsBuilder returnCount(boolean value) { - paramMap.put(AllConstants.RETURN_COUNT, value); - return this; - } - - public RequestParamsBuilder addParam(String key, Object value) { - paramMap.put(key, value); - return this; - } - - public RequestParamsBuilder removeParam(String key) { - paramMap.remove(key); - return this; - } - - public String build() { - - for (Map.Entry entry : paramMap.entrySet()) { - - if (CoreLibrary.getInstance().getSyncConfiguration().isSyncUsingPost()) { - - try { - postSyncParamsBuilder.put(entry.getKey(), entry.getValue()); - } catch (JSONException e) { - Timber.e(e); - } - - } else { - - if (0 != getSyncParamsBuilder.length()) { - getSyncParamsBuilder.append('&'); - } - - getSyncParamsBuilder.append(entry.getKey()).append('=').append(entry.getValue()); - } - - } - - return CoreLibrary.getInstance().getSyncConfiguration().isSyncUsingPost() ? postSyncParamsBuilder.toString() : getSyncParamsBuilder.toString(); - } - - @Override - public String toString() { - return build(); - } - - } } diff --git a/opensrp-core/src/main/java/org/smartregister/sync/intent/CampaignIntentService.java b/opensrp-core/src/main/java/org/smartregister/sync/intent/CampaignIntentService.java index 4122ded38e..b869a7deb5 100644 --- a/opensrp-core/src/main/java/org/smartregister/sync/intent/CampaignIntentService.java +++ b/opensrp-core/src/main/java/org/smartregister/sync/intent/CampaignIntentService.java @@ -28,6 +28,7 @@ import static org.smartregister.AllConstants.CAMPAIGNS; +@Deprecated public class CampaignIntentService extends BaseSyncIntentService { public static final String CAMPAIGN_URL = "/rest/campaign/"; private static final String TAG = "CampaignIntentService"; diff --git a/opensrp-core/src/main/java/org/smartregister/sync/intent/DocumentConfigurationIntentService.java b/opensrp-core/src/main/java/org/smartregister/sync/intent/DocumentConfigurationIntentService.java index c4322cef84..566051bf2d 100644 --- a/opensrp-core/src/main/java/org/smartregister/sync/intent/DocumentConfigurationIntentService.java +++ b/opensrp-core/src/main/java/org/smartregister/sync/intent/DocumentConfigurationIntentService.java @@ -19,6 +19,7 @@ * * @author cozej4 https://github.com/cozej4 */ +@Deprecated public class DocumentConfigurationIntentService extends BaseSyncIntentService { private HTTPAgent httpAgent; private ManifestRepository manifestRepository; diff --git a/opensrp-core/src/main/java/org/smartregister/sync/intent/ExtendedSyncIntentService.java b/opensrp-core/src/main/java/org/smartregister/sync/intent/ExtendedSyncIntentService.java index f6ddccdc38..8a7aafdf96 100644 --- a/opensrp-core/src/main/java/org/smartregister/sync/intent/ExtendedSyncIntentService.java +++ b/opensrp-core/src/main/java/org/smartregister/sync/intent/ExtendedSyncIntentService.java @@ -9,7 +9,7 @@ import timber.log.Timber; - +@Deprecated public class ExtendedSyncIntentService extends BaseSyncIntentService { private ActionService actionService = CoreLibrary.getInstance().context().actionService(); diff --git a/opensrp-core/src/main/java/org/smartregister/sync/intent/LocationIntentService.java b/opensrp-core/src/main/java/org/smartregister/sync/intent/LocationIntentService.java index de27e4cac7..b15da8793c 100644 --- a/opensrp-core/src/main/java/org/smartregister/sync/intent/LocationIntentService.java +++ b/opensrp-core/src/main/java/org/smartregister/sync/intent/LocationIntentService.java @@ -11,6 +11,7 @@ import org.smartregister.util.DateTimeTypeConverter; import org.smartregister.util.PropertiesConverter; +@Deprecated public class LocationIntentService extends BaseSyncIntentService { private static final String TAG = "LocationIntentService"; public static Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ") diff --git a/opensrp-core/src/main/java/org/smartregister/sync/intent/PlanIntentService.java b/opensrp-core/src/main/java/org/smartregister/sync/intent/PlanIntentService.java index c1b837f4a8..63d002ab67 100644 --- a/opensrp-core/src/main/java/org/smartregister/sync/intent/PlanIntentService.java +++ b/opensrp-core/src/main/java/org/smartregister/sync/intent/PlanIntentService.java @@ -8,6 +8,7 @@ /** * Created by Vincent Karuri on 08/05/2019 */ +@Deprecated public class PlanIntentService extends BaseSyncIntentService { private static final String TAG = "PlanIntentService"; diff --git a/opensrp-core/src/main/java/org/smartregister/sync/intent/SyncAllLocationsIntentService.java b/opensrp-core/src/main/java/org/smartregister/sync/intent/SyncAllLocationsIntentService.java index 2a18d01233..d38a60307e 100644 --- a/opensrp-core/src/main/java/org/smartregister/sync/intent/SyncAllLocationsIntentService.java +++ b/opensrp-core/src/main/java/org/smartregister/sync/intent/SyncAllLocationsIntentService.java @@ -12,7 +12,7 @@ import org.smartregister.util.PropertiesConverter; import timber.log.Timber; - +@Deprecated public class SyncAllLocationsIntentService extends BaseSyncIntentService { private static final String TAG = "SyncAllLocationsIntentService"; diff --git a/opensrp-core/src/main/java/org/smartregister/sync/intent/SyncIntentService.java b/opensrp-core/src/main/java/org/smartregister/sync/intent/SyncIntentService.java index 294a1bb415..ce8c0549b9 100644 --- a/opensrp-core/src/main/java/org/smartregister/sync/intent/SyncIntentService.java +++ b/opensrp-core/src/main/java/org/smartregister/sync/intent/SyncIntentService.java @@ -41,6 +41,7 @@ import org.smartregister.repository.AllSharedPreferences; import org.smartregister.repository.EventClientRepository; import org.smartregister.service.HTTPAgent; +import org.smartregister.sync.RequestParamsBuilder; import org.smartregister.sync.helper.ECSyncHelper; import org.smartregister.sync.helper.ValidateAssignmentHelper; import org.smartregister.util.NetworkUtils; @@ -170,7 +171,7 @@ private synchronized void fetchRetry(final int count, boolean returnCount) { startEventTrace(FETCH, 0); - BaseSyncIntentService.RequestParamsBuilder syncParamBuilder = new BaseSyncIntentService.RequestParamsBuilder(). + RequestParamsBuilder syncParamBuilder = new RequestParamsBuilder(). configureSyncFilter(configs.getSyncFilterParam().value(), configs.getSyncFilterValue()).addServerVersion(lastSyncDatetime).addEventPullLimit(getEventPullLimit()); Response resp = getUrlResponse(baseUrl + SYNC_URL, syncParamBuilder, configs, returnCount); @@ -216,7 +217,7 @@ private synchronized void fetchRetry(final int count, boolean returnCount) { * @param configs the Sync Configuration object with various configurations * @param returnCount a boolean flag, whether to return the total count of records as part of the response (field - total_records) */ - protected Response getUrlResponse(@NonNull String baseURL, @NonNull BaseSyncIntentService.RequestParamsBuilder requestParamsBuilder, @NonNull SyncConfiguration configs, boolean returnCount) { + protected Response getUrlResponse(@NonNull String baseURL, @NonNull RequestParamsBuilder requestParamsBuilder, @NonNull SyncConfiguration configs, boolean returnCount) { Response response; String requestUrl = baseURL; diff --git a/opensrp-core/src/main/java/org/smartregister/sync/intent/SyncLocationsByLevelAndTagsIntentService.java b/opensrp-core/src/main/java/org/smartregister/sync/intent/SyncLocationsByLevelAndTagsIntentService.java index 197c8b7dbb..2704b2e7ee 100644 --- a/opensrp-core/src/main/java/org/smartregister/sync/intent/SyncLocationsByLevelAndTagsIntentService.java +++ b/opensrp-core/src/main/java/org/smartregister/sync/intent/SyncLocationsByLevelAndTagsIntentService.java @@ -6,6 +6,7 @@ import timber.log.Timber; +@Deprecated public class SyncLocationsByLevelAndTagsIntentService extends BaseSyncIntentService { private static final String TAG = "SyncLocationsByLevelAndTagsIntentService"; diff --git a/opensrp-core/src/main/java/org/smartregister/sync/intent/SyncLocationsByTeamIdsIntentService.java b/opensrp-core/src/main/java/org/smartregister/sync/intent/SyncLocationsByTeamIdsIntentService.java index 987a069a01..c395d6627a 100644 --- a/opensrp-core/src/main/java/org/smartregister/sync/intent/SyncLocationsByTeamIdsIntentService.java +++ b/opensrp-core/src/main/java/org/smartregister/sync/intent/SyncLocationsByTeamIdsIntentService.java @@ -6,6 +6,7 @@ import timber.log.Timber; +@Deprecated public class SyncLocationsByTeamIdsIntentService extends BaseSyncIntentService { private static final String TAG = "SyncLocationsByTeamIdsIntentService"; diff --git a/opensrp-core/src/main/java/org/smartregister/sync/intent/SyncTaskIntentService.java b/opensrp-core/src/main/java/org/smartregister/sync/intent/SyncTaskIntentService.java index 87c217b5b4..6a3385ff49 100644 --- a/opensrp-core/src/main/java/org/smartregister/sync/intent/SyncTaskIntentService.java +++ b/opensrp-core/src/main/java/org/smartregister/sync/intent/SyncTaskIntentService.java @@ -4,6 +4,7 @@ import org.smartregister.sync.helper.TaskServiceHelper; +@Deprecated public class SyncTaskIntentService extends BaseSyncIntentService { private static final String TAG = "SyncTaskIntentService"; private TaskServiceHelper taskServiceHelper; diff --git a/opensrp-core/src/main/java/org/smartregister/sync/intent/ValidateIntentService.java b/opensrp-core/src/main/java/org/smartregister/sync/intent/ValidateIntentService.java index a91dafa15b..220d1cd359 100644 --- a/opensrp-core/src/main/java/org/smartregister/sync/intent/ValidateIntentService.java +++ b/opensrp-core/src/main/java/org/smartregister/sync/intent/ValidateIntentService.java @@ -28,6 +28,7 @@ /** * Created by keyman on 11/10/2017. */ +@Deprecated public class ValidateIntentService extends BaseSyncIntentService { private Context context; diff --git a/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/BaseWorker.kt b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/BaseWorker.kt new file mode 100644 index 0000000000..5266743412 --- /dev/null +++ b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/BaseWorker.kt @@ -0,0 +1,22 @@ +package org.smartregister.sync.wm.worker + +import android.content.Context +import androidx.work.Worker +import androidx.work.WorkerParameters +import org.smartregister.CoreLibrary + +abstract class BaseWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams){ + + fun beforeWork(){ + val coreLibrary = CoreLibrary.getInstance() + val syncConfiguration = coreLibrary.syncConfiguration + if (syncConfiguration != null){ + coreLibrary.context().httpAgent + .apply { + connectTimeout = syncConfiguration.connectTimeout + readTimeout = syncConfiguration.readTimeout + } + } + } + +} \ No newline at end of file diff --git a/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/CampaignWorker.kt b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/CampaignWorker.kt new file mode 100644 index 0000000000..035f6d83c1 --- /dev/null +++ b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/CampaignWorker.kt @@ -0,0 +1,91 @@ +package org.smartregister.sync.wm.worker + +import android.content.Context +import androidx.work.Worker +import androidx.work.WorkerParameters +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.reflect.TypeToken +import org.joda.time.DateTime +import org.joda.time.LocalDate +import org.smartregister.AllConstants +import org.smartregister.CoreLibrary +import org.smartregister.domain.Campaign +import org.smartregister.domain.FetchStatus +import org.smartregister.exception.NoHttpResponseException +import org.smartregister.service.HTTPAgent +import org.smartregister.util.DateTimeTypeConverter +import org.smartregister.util.DateTypeConverter +import org.smartregister.util.Utils +import org.smartregister.util.WorkerNotificationDelegate +import timber.log.Timber + +class CampaignWorker(context: Context, workerParams: WorkerParameters) : + BaseWorker(context, workerParams) { + + private val notificationDelegate = WorkerNotificationDelegate(context, TAG) + + override fun doWork(): Result { + beforeWork() + + notificationDelegate.notify("Running \u8086") + + val opensrpContext = CoreLibrary.getInstance().context() + val baseUrl = opensrpContext.configuration().dristhiBaseURL() + val allSharedPreferences = opensrpContext.allSharedPreferences() + val campaignRepository = opensrpContext.campaignRepository + val httpAgent = opensrpContext.httpAgent + return try { + val campaignsResponse = fetchCampaigns(httpAgent, baseUrl) + val allowedCampaigns = allSharedPreferences.getPreference(AllConstants.CAMPAIGNS).split(",") + val campaigns = gson.fromJson>( + campaignsResponse, + object : TypeToken?>() {}.type + ) + val errors = mutableListOf() + campaigns.filter { it.identifier != null && it.identifier in allowedCampaigns } + .forEach { + runCatching { campaignRepository.addOrUpdate(it)}.onFailure { e -> errors.add(e) } + } + if (errors.isNotEmpty()) throw Exception(errors.random()) + + Result.success().apply { + notificationDelegate.notify("Success!!") + } + } catch (e: Exception) { + Timber.e(e) + Result.failure().apply { + notificationDelegate.notify("Error: ${e.message}") + } + } + } + + fun getUrl(baseUrl: String): String { + val endString = "/" + return "${if (baseUrl.endsWith(endString)) baseUrl.substring(0, baseUrl.lastIndexOf(endString)) else baseUrl}$CAMPAIGN_URL" + } + + @Throws(NoHttpResponseException::class) + fun fetchCampaigns(httpAgent: HTTPAgent?, baseUrl: String): String { + if (httpAgent == null) { + applicationContext.sendBroadcast(Utils.completeSync(FetchStatus.noConnection)) + throw IllegalArgumentException("$CAMPAIGN_URL http agent is null") + } + val resp= httpAgent.fetch(getUrl(baseUrl)) + if (resp.isFailure) { + applicationContext.sendBroadcast(Utils.completeSync(FetchStatus.nothingFetched)) + throw NoHttpResponseException("$CAMPAIGN_URL not returned data") + } + return resp.payload().toString() + } + + companion object{ + const val CAMPAIGN_URL = "/rest/campaign/" + const val TAG = "CampaignWorker" + val gson: Gson = GsonBuilder().registerTypeAdapter( + DateTime::class.java, + DateTimeTypeConverter("yyyy-MM-dd'T'HHmm") + ) + .registerTypeAdapter(LocalDate::class.java, DateTypeConverter()).create() + } +} \ No newline at end of file diff --git a/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/DocumentConfigurationWorker.kt b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/DocumentConfigurationWorker.kt new file mode 100644 index 0000000000..fadbd6d46a --- /dev/null +++ b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/DocumentConfigurationWorker.kt @@ -0,0 +1,42 @@ +package org.smartregister.sync.wm.worker + +import android.content.Context +import androidx.work.WorkerParameters +import org.smartregister.CoreLibrary +import org.smartregister.service.DocumentConfigurationService +import org.smartregister.util.WorkerNotificationDelegate +import timber.log.Timber + +class DocumentConfigurationWorker(context: Context, workerParams: WorkerParameters): BaseWorker(context, workerParams) { + + private val notificationDelegate = WorkerNotificationDelegate(context, TAG) + + override fun doWork(): Result { + beforeWork() + + notificationDelegate.notify("Running \u8086") + return try { + val openSrpContext = CoreLibrary.getInstance().context() + val httpAgent = openSrpContext.httpAgent + val manifestRepository = openSrpContext.manifestRepository + val clientFormRepository = openSrpContext.clientFormRepository + val configuration = openSrpContext.configuration() + DocumentConfigurationService(httpAgent, manifestRepository, clientFormRepository, configuration) + .fetchManifest() + + Result.success().apply { + notificationDelegate.notify("Success!!") + } + } catch (e:Exception){ + Timber.e(e) + Result.failure().apply { + notificationDelegate.notify("Error: ${e.message}") + } + } + + } + + companion object{ + const val TAG = "DocumentConfigurationWorker" + } +} \ No newline at end of file diff --git a/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/ExtendedSyncWorker.kt b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/ExtendedSyncWorker.kt new file mode 100644 index 0000000000..a88f779f88 --- /dev/null +++ b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/ExtendedSyncWorker.kt @@ -0,0 +1,44 @@ +package org.smartregister.sync.wm.worker + +import android.content.Context +import androidx.work.WorkerParameters +import org.smartregister.CoreLibrary +import org.smartregister.sync.wm.workerrequest.OneTimeNetworkWorkRequest +import org.smartregister.util.WorkerNotificationDelegate +import timber.log.Timber + +class ExtendedSyncWorker(context: Context, workerParams: WorkerParameters) : + BaseWorker(context, workerParams) { + private val notificationDelegate = WorkerNotificationDelegate(context, TAG) + + override fun doWork(): Result { + beforeWork() + + notificationDelegate.notify("Running \u8086") + return try { + val coreLibrary = CoreLibrary.getInstance() + val actionService = coreLibrary.context().actionService() + val syncConfiguration = coreLibrary.syncConfiguration + if (syncConfiguration != null && !syncConfiguration.disableActionService()) actionService.fetchNewActions() + startSyncValidation() + + Result.success().apply { + notificationDelegate.notify("Success!!") + } + } catch (e: Exception) { + Timber.e(e) + Result.failure().apply { + notificationDelegate.notify("Error: ${e.message}") + } + } + + } + + private fun startSyncValidation(){ + OneTimeNetworkWorkRequest.runTask(applicationContext, ValidateSyncWorker::class.java) + } + + companion object { + const val TAG = "ExtendedSyncWorker" + } +} \ No newline at end of file diff --git a/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/LocationWorker.kt b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/LocationWorker.kt new file mode 100644 index 0000000000..decefc8d57 --- /dev/null +++ b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/LocationWorker.kt @@ -0,0 +1,38 @@ +package org.smartregister.sync.wm.worker + +import android.content.Context +import androidx.work.WorkerParameters +import org.smartregister.sync.helper.LocationServiceHelper +import org.smartregister.util.WorkerNotificationDelegate +import timber.log.Timber + +class LocationWorker(context: Context, workerParams: WorkerParameters) : + BaseWorker(context, workerParams) { + private val notificationDelegate = WorkerNotificationDelegate(context, TAG) + + override fun doWork(): Result { + beforeWork() + + notificationDelegate.notify("Running \u8086") + return try { + LocationServiceHelper.getInstance() + .apply { + fetchLocationsStructures() + } + + Result.success().apply { + notificationDelegate.notify("Success!!") + } + } catch (e: Exception) { + Timber.e(e) + Result.failure().apply { + notificationDelegate.notify("Error: ${e.message}") + } + } + + } + + companion object { + const val TAG = "LocationWorker" + } +} \ No newline at end of file diff --git a/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/PlanWorker.kt b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/PlanWorker.kt new file mode 100644 index 0000000000..41c7b8869e --- /dev/null +++ b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/PlanWorker.kt @@ -0,0 +1,34 @@ +package org.smartregister.sync.wm.worker + +import android.content.Context +import androidx.work.WorkerParameters +import org.smartregister.sync.helper.PlanIntentServiceHelper +import org.smartregister.util.WorkerNotificationDelegate +import timber.log.Timber + +class PlanWorker(context: Context, workerParams: WorkerParameters) : + BaseWorker(context, workerParams) { + private val notificationDelegate = WorkerNotificationDelegate(context, TAG) + + override fun doWork(): Result { + beforeWork() + + notificationDelegate.notify("Running \u8086") + return try { + PlanIntentServiceHelper.getInstance().syncPlans() + Result.success().apply { + notificationDelegate.notify("Success!!") + } + } catch (e: Exception) { + Timber.e(e) + Result.failure().apply { + notificationDelegate.notify("Error: ${e.message}") + } + } + + } + + companion object { + const val TAG = "PlanWorker" + } +} \ No newline at end of file diff --git a/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SyncAllLocationsWorker.kt b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SyncAllLocationsWorker.kt new file mode 100644 index 0000000000..ca21ad8c09 --- /dev/null +++ b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SyncAllLocationsWorker.kt @@ -0,0 +1,39 @@ +package org.smartregister.sync.wm.worker + +import android.content.Context +import androidx.work.Worker +import androidx.work.WorkerParameters +import org.smartregister.sync.helper.LocationServiceHelper +import org.smartregister.util.WorkerNotificationDelegate +import timber.log.Timber + +class SyncAllLocationsWorker(context: Context, workerParams: WorkerParameters): BaseWorker(context, workerParams) { + + private val notificationDelegate = WorkerNotificationDelegate(context, TAG) + + override fun doWork(): Result { + beforeWork() + + notificationDelegate.notify("Running \u8086") + val locationServiceHelper = LocationServiceHelper.getInstance() + + return try { + locationServiceHelper.fetchAllLocations() + .runCatching { + + } + Result.success().apply { + notificationDelegate.notify("Success!!") + } + } catch (e: Exception) { + Timber.e(e) + Result.failure().apply { + notificationDelegate.notify("Error: ${e.message}") + } + } + } + + companion object{ + const val TAG = "SyncAllLocationsWorker" + } +} diff --git a/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SyncLocationsByLevelAndTagsWorker.kt b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SyncLocationsByLevelAndTagsWorker.kt new file mode 100644 index 0000000000..da4aa78429 --- /dev/null +++ b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SyncLocationsByLevelAndTagsWorker.kt @@ -0,0 +1,37 @@ +package org.smartregister.sync.wm.worker + +import android.content.Context +import androidx.work.WorkerParameters +import org.smartregister.sync.helper.LocationServiceHelper +import org.smartregister.util.WorkerNotificationDelegate +import timber.log.Timber + +class SyncLocationsByLevelAndTagsWorker(context: Context, workerParams: WorkerParameters) : + BaseWorker(context, workerParams) { + private val notificationDelegate = WorkerNotificationDelegate(context, TAG) + + override fun doWork(): Result { + beforeWork() + + notificationDelegate.notify("Running \u8086") + return try { + LocationServiceHelper.getInstance() + .apply { + fetchLocationsByLevelAndTags() + } + Result.success().apply { + notificationDelegate.notify("Success!!") + } + } catch (e: Exception) { + Timber.e(e) + Result.failure().apply { + notificationDelegate.notify("Error: ${e.message}") + } + } + + } + + companion object { + const val TAG = "SyncLocationsByLevelAndTagsWorker" + } +} \ No newline at end of file diff --git a/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SyncLocationsByTeamIdsWorker.kt b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SyncLocationsByTeamIdsWorker.kt new file mode 100644 index 0000000000..1bb5a3e0e8 --- /dev/null +++ b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SyncLocationsByTeamIdsWorker.kt @@ -0,0 +1,37 @@ +package org.smartregister.sync.wm.worker + +import android.content.Context +import androidx.work.WorkerParameters +import org.smartregister.sync.helper.LocationServiceHelper +import org.smartregister.util.WorkerNotificationDelegate +import timber.log.Timber + +class SyncLocationsByTeamIdsWorker(context: Context, workerParams: WorkerParameters) : + BaseWorker(context, workerParams) { + private val notificationDelegate = WorkerNotificationDelegate(context, TAG) + + override fun doWork(): Result { + beforeWork() + + notificationDelegate.notify("Running \u8086") + return try { + LocationServiceHelper.getInstance() + .apply { + fetchOpenMrsLocationsByTeamIds() + } + Result.success().apply { + notificationDelegate.notify("Success!!") + } + } catch (e: Exception) { + Timber.e(e) + Result.failure().apply { + notificationDelegate.notify("Error: ${e.message}") + } + } + + } + + companion object { + const val TAG = "SyncLocationsByTeamIdsWorker" + } +} \ No newline at end of file diff --git a/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SyncTaskWorker.kt b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SyncTaskWorker.kt new file mode 100644 index 0000000000..47ab04c421 --- /dev/null +++ b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SyncTaskWorker.kt @@ -0,0 +1,38 @@ +package org.smartregister.sync.wm.worker + +import android.content.Context +import androidx.work.WorkerParameters +import org.smartregister.sync.helper.TaskServiceHelper +import org.smartregister.util.WorkerNotificationDelegate +import timber.log.Timber + +class SyncTaskWorker(context: Context, workerParams: WorkerParameters) : + BaseWorker(context, workerParams) { + private val notificationDelegate = WorkerNotificationDelegate(context, TAG) + + override fun doWork(): Result { + beforeWork() + + notificationDelegate.notify("Running \u8086") + return try { + TaskServiceHelper.getInstance() + .apply { + syncTasks() + } + + Result.success().apply { + notificationDelegate.notify("Success!!") + } + } catch (e: Exception) { + Timber.e(e) + Result.failure().apply { + notificationDelegate.notify("Error: ${e.message}") + } + } + + } + + companion object { + const val TAG = "SyncTaskWorker" + } +} \ No newline at end of file diff --git a/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/ValidateSyncWorker.kt b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/ValidateSyncWorker.kt new file mode 100644 index 0000000000..61dc5eb7bf --- /dev/null +++ b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/ValidateSyncWorker.kt @@ -0,0 +1,158 @@ +package org.smartregister.sync.wm.worker + +import android.content.Context +import androidx.work.WorkerParameters +import org.apache.commons.lang3.StringUtils +import org.json.JSONArray +import org.json.JSONObject +import org.smartregister.AllConstants +import org.smartregister.CoreLibrary +import org.smartregister.R +import org.smartregister.domain.Client +import org.smartregister.domain.Event +import org.smartregister.domain.Response +import org.smartregister.repository.EventClientRepository +import org.smartregister.service.HTTPAgent +import org.smartregister.util.WorkerNotificationDelegate +import timber.log.Timber +import java.text.MessageFormat +import java.util.function.Function +import java.util.function.Predicate +import java.util.stream.Collectors + +class ValidateSyncWorker(context: Context, workerParams: WorkerParameters) : + BaseWorker(context, workerParams) { + private val notificationDelegate = WorkerNotificationDelegate(context, TAG) + + override fun doWork(): Result { + beforeWork() + + notificationDelegate.notify("Running \u8086") + return try { + val openSrpContext = CoreLibrary.getInstance().context() + val baseUrl = openSrpContext.configuration().dristhiBaseURL().let { + val urlSeparator = applicationContext.getString(R.string.url_separator) + if (it.endsWith(urlSeparator)) it.substring(0, it.lastIndexOf(urlSeparator)) else it + } + validateSync(openSrpContext.httpAgent, openSrpContext.eventClientRepository, baseUrl) + + Result.success().apply { + notificationDelegate.notify("Success!!") + } + } catch (e: Exception) { + Timber.e(e) + Result.failure().apply { + notificationDelegate.notify("Error: ${e.message}") + } + } + + } + + private fun validateSync(httpAgent: HTTPAgent, eventClientRepository: EventClientRepository, baseUrl: String, initialFetchLimit: Int = FETCH_LIMIT){ + var fetchLimit = initialFetchLimit + val clientIds: MutableList = + eventClientRepository.getUnValidatedClientBaseEntityIds(fetchLimit) + if (clientIds.isNotEmpty()) { + fetchLimit -= clientIds.size + } + + var eventIds: MutableList = ArrayList() + if (fetchLimit > 0) { + eventIds = eventClientRepository.getUnValidatedEventFormSubmissionIds(fetchLimit) + } + + val request: JSONObject = request(clientIds, eventIds) ?: return + + val jsonPayload = request.toString() + val response: Response = httpAgent.postWithJsonResponse( + MessageFormat.format( + "{0}/{1}", + baseUrl, + VALIDATE_SYNC_PATH + ), + jsonPayload + ) + if (response.isFailure || StringUtils.isBlank(response.payload())) { + Timber.e("Validation sync failed.") + return + } + + val results = JSONObject(response.payload()) + + if (results.has(AllConstants.KEY.CLIENTS)) { + val inValidClients = results.getJSONArray(AllConstants.KEY.CLIENTS) + val invalidClientIds: Set = filterArchivedClients(eventClientRepository, extractIds(inValidClients)) + for (id in invalidClientIds) { + clientIds.remove(id) + eventClientRepository.markClientValidationStatus(id, false) + } + for (clientId in clientIds) { + eventClientRepository.markClientValidationStatus(clientId, true) + } + } + + if (results.has(AllConstants.KEY.EVENTS)) { + val inValidEvents = results.getJSONArray(AllConstants.KEY.EVENTS) + val inValidEventIds: Set = filterArchivedEvents(eventClientRepository, extractIds(inValidEvents)) + for (inValidEventId in inValidEventIds) { + eventIds.remove(inValidEventId) + eventClientRepository.markEventValidationStatus(inValidEventId, false) + } + for (eventId in eventIds) { + eventClientRepository.markEventValidationStatus(eventId, true) + } + } + } + + private fun extractIds(inValidClients: JSONArray): Set { + val ids: MutableSet = HashSet() + for (i in 0 until inValidClients.length()) { + ids.add(inValidClients.optString(i)) + } + return ids + } + + private fun filterArchivedClients(eventClientRepository: EventClientRepository, ids: Set): Set { + return eventClientRepository.fetchClientByBaseEntityIds(ids) + .stream() + .filter(Predicate { c: Client -> c.dateVoided == null }) + .map(Function { obj: Client -> obj.baseEntityId }) + .collect(Collectors.toSet()) + } + + private fun filterArchivedEvents(eventClientRepository: EventClientRepository, ids: Set): Set { + return eventClientRepository.getEventsByEventIds(ids) + .stream() + .filter(Predicate { e: Event -> e.dateVoided == null }) + .map(Function { obj: Event -> obj.eventId }) + .collect(Collectors.toSet()) + } + + private fun request(clientIds: List, eventIds: List): JSONObject? { + var clientIdArray: JSONArray? = null + if (clientIds.isNotEmpty()) { + clientIdArray = JSONArray(clientIds) + } + var eventIdArray: JSONArray? = null + if (eventIds.isNotEmpty()) { + eventIdArray = JSONArray(eventIds) + } + if (clientIdArray != null || eventIdArray != null) { + val request = JSONObject() + if (clientIdArray != null) { + request.put(AllConstants.KEY.CLIENTS, clientIdArray) + } + if (eventIdArray != null) { + request.put(AllConstants.KEY.EVENTS, eventIdArray) + } + return request + } + return null + } + + companion object { + const val TAG = "ValidateSyncWorker" + private const val FETCH_LIMIT = 100 + private const val VALIDATE_SYNC_PATH = "rest/validate/sync" + } +} \ No newline at end of file diff --git a/opensrp-core/src/main/java/org/smartregister/sync/wm/workerrequest/OneTimeNetworkWorkRequest.kt b/opensrp-core/src/main/java/org/smartregister/sync/wm/workerrequest/OneTimeNetworkWorkRequest.kt new file mode 100644 index 0000000000..55313d740b --- /dev/null +++ b/opensrp-core/src/main/java/org/smartregister/sync/wm/workerrequest/OneTimeNetworkWorkRequest.kt @@ -0,0 +1,34 @@ +package org.smartregister.sync.wm.workerrequest + +import android.content.Context +import android.os.Build +import androidx.work.Constraints +import androidx.work.Data +import androidx.work.ListenableWorker +import androidx.work.NetworkType +import androidx.work.OneTimeWorkRequest +import androidx.work.WorkManager + + +object OneTimeNetworkWorkRequest { + + fun runTask(context: Context, workerClass: Class) { + val inputData: Data = Data.Builder() + .putString("key", "value") + .build() + + val constraintsBuilder = Constraints.Builder() + .setRequiredNetworkType(NetworkType.UNMETERED) + .setRequiresBatteryNotLow(false) + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.M){ + constraintsBuilder.setRequiresDeviceIdle(false) + } + val constraints: Constraints = constraintsBuilder.build() + val request = OneTimeWorkRequest.Builder(workerClass) + .setInputData(inputData) + .setConstraints(constraints) + .build() + WorkManager.getInstance(context).enqueue(request) + } + +} \ No newline at end of file diff --git a/opensrp-core/src/main/java/org/smartregister/util/WorkerUtils.kt b/opensrp-core/src/main/java/org/smartregister/util/WorkerUtils.kt new file mode 100644 index 0000000000..ed0161515f --- /dev/null +++ b/opensrp-core/src/main/java/org/smartregister/util/WorkerUtils.kt @@ -0,0 +1,53 @@ +package org.smartregister.util + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import android.os.Build +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import org.smartregister.R +import kotlin.random.Random + + +object WorkerUtils { + private const val CHANNEL_ID = "org.smartregisterx" + private const val CHANNEL_NAME = "OpenSRP" + private const val CHANNEL_DESC = "OpenSRP Client" + + fun makeStatusNotification( + context: Context, + notificationId: Int, + title: String?, + message: String? + ) { + val notificationManager = NotificationManagerCompat.from(context) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel( + CHANNEL_ID, + CHANNEL_NAME, + NotificationManager.IMPORTANCE_LOW + ) + channel.description = CHANNEL_DESC + notificationManager.createNotificationChannel(channel) + } + val builder: NotificationCompat.Builder = NotificationCompat.Builder(context, CHANNEL_ID) + .setContentTitle(title) + .setContentText(message) + .setSmallIcon(R.drawable.ic_opensrp_logo) + .setPriority(NotificationCompat.PRIORITY_LOW) + notificationManager.notify(notificationId, builder.build()) + } + + fun dismissNotification(context: Context, notificationId: Int){ + NotificationManagerCompat.from(context).cancel(notificationId) + } +} + +class WorkerNotificationDelegate(private val context: Context, + private val title: String?){ + private val notificationId: Int = Random.nextInt() + fun notify(message: String){ + WorkerUtils.makeStatusNotification(context, notificationId, title, message) + } +} \ No newline at end of file diff --git a/opensrp-core/src/test/java/org/smartregister/sync/intent/SyncIntentServiceTest.java b/opensrp-core/src/test/java/org/smartregister/sync/intent/SyncIntentServiceTest.java index 74af51fbd5..d2fce52a69 100644 --- a/opensrp-core/src/test/java/org/smartregister/sync/intent/SyncIntentServiceTest.java +++ b/opensrp-core/src/test/java/org/smartregister/sync/intent/SyncIntentServiceTest.java @@ -27,7 +27,6 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; import org.powermock.reflect.Whitebox; import androidx.test.core.app.ApplicationProvider; import org.robolectric.util.ReflectionHelpers; @@ -43,6 +42,7 @@ import org.smartregister.receiver.SyncStatusBroadcastReceiver; import org.smartregister.repository.EventClientRepository; import org.smartregister.service.HTTPAgent; +import org.smartregister.sync.RequestParamsBuilder; import org.smartregister.util.SyncUtils; import java.io.IOException; @@ -553,7 +553,7 @@ public void testGetUrlResponseCreatesValidUrlWithExtraParamsUsingGET() { Mockito.doReturn(new Response<>(responseStatus, null)) .when(httpAgent).fetch(stringArgumentCaptor.capture()); String removeParamKey = "some-other-param-to-remove"; - BaseSyncIntentService.RequestParamsBuilder builder = new BaseSyncIntentService.RequestParamsBuilder().configureSyncFilter("locationId", "location-1") + RequestParamsBuilder builder = new RequestParamsBuilder().configureSyncFilter("locationId", "location-1") .addServerVersion(0).addEventPullLimit(250).addParam("region", "au-west").addParam("is_enabled", true).addParam("some-other-param", 85l) .addParam(removeParamKey, 745).removeParam(removeParamKey); syncIntentService.getUrlResponse("https://sample-stage.smartregister.org/opensrp/rest/event/sync", builder, syncConfiguration, false); @@ -573,7 +573,7 @@ public void testGetUrlResponseCreatesValidUrlWithExtraParamsUsingPost() { Mockito.doReturn(new Response<>(responseStatus, null)) .when(httpAgent).postWithJsonResponse(ArgumentMatchers.anyString(), stringArgumentCaptor.capture()); - BaseSyncIntentService.RequestParamsBuilder builder = new BaseSyncIntentService.RequestParamsBuilder().configureSyncFilter("locationId", "location-2") + RequestParamsBuilder builder = new RequestParamsBuilder().configureSyncFilter("locationId", "location-2") .addServerVersion(0).addEventPullLimit(500).addParam("region", "au-east").addParam("is_enabled", false).addParam("some-other-param", 36); syncIntentService.getUrlResponse("https://sample-stage.smartregister.org/opensrp/rest/event/sync", builder, syncConfiguration, true); diff --git a/sample/src/main/java/org/smartregister/sample/MainActivity.java b/sample/src/main/java/org/smartregister/sample/MainActivity.java index 3935af6fe8..9560168ff0 100644 --- a/sample/src/main/java/org/smartregister/sample/MainActivity.java +++ b/sample/src/main/java/org/smartregister/sample/MainActivity.java @@ -22,6 +22,8 @@ import org.joda.time.LocalDate; import org.smartregister.cursoradapter.SmartRegisterQueryBuilder; import org.smartregister.sample.fragment.ReportFragment; +import org.smartregister.sync.wm.worker.SyncAllLocationsWorker; +import org.smartregister.sync.wm.workerrequest.OneTimeNetworkWorkRequest; import org.smartregister.util.AppHealthUtils; import org.smartregister.util.DateUtil; import org.smartregister.util.LangUtils; @@ -140,6 +142,11 @@ public void onNothingSelected(AdapterView parent) { ((TextView) findViewById(R.id.time)).setText(DateUtil.getDuration(new DateTime().minusYears(4).minusMonths(3).minusWeeks(2).minusDays(1))); new AppHealthUtils(findViewById(R.id.show_sync_stats)); + findViewById(R.id.wmSync).setOnClickListener(v -> { + v.setVisibility(View.INVISIBLE); + OneTimeNetworkWorkRequest.INSTANCE + .runTask(getApplicationContext(), SyncAllLocationsWorker.class); + }); } @Override diff --git a/sample/src/main/res/layout-v17/content_main.xml b/sample/src/main/res/layout-v17/content_main.xml index 0ab437a387..13fa0888ce 100644 --- a/sample/src/main/res/layout-v17/content_main.xml +++ b/sample/src/main/res/layout-v17/content_main.xml @@ -92,4 +92,12 @@ +