From f1943aa2e0d6ba8c1301b1a99fb45d2776f8e2d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=E2=89=A1ZRS?= <12814349+LZRS@users.noreply.github.com> Date: Tue, 11 Oct 2022 09:52:46 +0300 Subject: [PATCH] Worker alternatives to sync intent services --- opensrp-core/build.gradle | 11 +- .../domain/db/EventClientQueryResult.java | 32 + .../repository/EventClientRepository.java | 6 +- .../sync/RequestParamsBuilder.java | 90 +++ .../helper/SyncSettingsServiceHelper.java | 4 +- .../sync/intent/BaseSyncIntentService.java | 79 +-- .../sync/intent/CampaignIntentService.java | 1 + .../DocumentConfigurationIntentService.java | 1 + .../intent/ExtendedSyncIntentService.java | 2 +- .../sync/intent/LocationIntentService.java | 1 + .../sync/intent/P2pProcessRecordsService.java | 37 +- .../sync/intent/PlanIntentService.java | 1 + .../intent/PullUniqueIdsIntentService.java | 1 + .../intent/SettingsSyncIntentService.java | 2 + .../intent/SyncAllLocationsIntentService.java | 2 +- .../sync/intent/SyncIntentService.java | 6 +- ...cLocationsByLevelAndTagsIntentService.java | 1 + .../SyncLocationsByTeamIdsIntentService.java | 1 + .../sync/intent/SyncTaskIntentService.java | 1 + .../sync/intent/ValidateIntentService.java | 1 + .../sync/wm/worker/BaseWorker.kt | 22 + .../sync/wm/worker/CampaignWorker.kt | 91 +++ .../wm/worker/DocumentConfigurationWorker.kt | 42 ++ .../sync/wm/worker/ExtendedSyncWorker.kt | 44 ++ .../sync/wm/worker/LocationWorker.kt | 38 ++ .../sync/wm/worker/P2pProcessRecordsWorker.kt | 87 +++ .../PlanPeriodicPlanEvaluationWorker.kt | 108 ++++ .../sync/wm/worker/PlanWorker.kt | 34 ++ .../sync/wm/worker/PullUniqueIdsWorker.kt | 84 +++ .../sync/wm/worker/SettingsSyncWorker.kt | 56 ++ .../sync/wm/worker/SyncAllLocationsWorker.kt | 39 ++ .../SyncLocationsByLevelAndTagsWorker.kt | 37 ++ .../wm/worker/SyncLocationsByTeamIdsWorker.kt | 37 ++ .../sync/wm/worker/SyncTaskWorker.kt | 38 ++ .../sync/wm/worker/SyncWorker.kt | 550 ++++++++++++++++++ .../sync/wm/worker/ValidateSyncWorker.kt | 158 +++++ .../sync/wm/workerrequest/SyncWorkRequest.kt | 44 ++ .../org/smartregister/util/WorkerUtils.kt | 53 ++ .../repository/EventClientRepositoryTest.java | 5 +- .../intent/P2pProcessRecordsServiceTest.java | 3 +- .../sync/intent/SyncIntentServiceTest.java | 6 +- .../smartregister/sample/MainActivity.java | 7 + .../src/main/res/layout-v17/content_main.xml | 8 + sample/src/main/res/layout/content_main.xml | 10 +- 44 files changed, 1753 insertions(+), 128 deletions(-) create mode 100644 opensrp-core/src/main/java/org/smartregister/domain/db/EventClientQueryResult.java create mode 100644 opensrp-core/src/main/java/org/smartregister/sync/RequestParamsBuilder.java create mode 100644 opensrp-core/src/main/java/org/smartregister/sync/wm/worker/BaseWorker.kt create mode 100644 opensrp-core/src/main/java/org/smartregister/sync/wm/worker/CampaignWorker.kt create mode 100644 opensrp-core/src/main/java/org/smartregister/sync/wm/worker/DocumentConfigurationWorker.kt create mode 100644 opensrp-core/src/main/java/org/smartregister/sync/wm/worker/ExtendedSyncWorker.kt create mode 100644 opensrp-core/src/main/java/org/smartregister/sync/wm/worker/LocationWorker.kt create mode 100644 opensrp-core/src/main/java/org/smartregister/sync/wm/worker/P2pProcessRecordsWorker.kt create mode 100644 opensrp-core/src/main/java/org/smartregister/sync/wm/worker/PlanPeriodicPlanEvaluationWorker.kt create mode 100644 opensrp-core/src/main/java/org/smartregister/sync/wm/worker/PlanWorker.kt create mode 100644 opensrp-core/src/main/java/org/smartregister/sync/wm/worker/PullUniqueIdsWorker.kt create mode 100644 opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SettingsSyncWorker.kt create mode 100644 opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SyncAllLocationsWorker.kt create mode 100644 opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SyncLocationsByLevelAndTagsWorker.kt create mode 100644 opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SyncLocationsByTeamIdsWorker.kt create mode 100644 opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SyncTaskWorker.kt create mode 100644 opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SyncWorker.kt create mode 100644 opensrp-core/src/main/java/org/smartregister/sync/wm/worker/ValidateSyncWorker.kt create mode 100644 opensrp-core/src/main/java/org/smartregister/sync/wm/workerrequest/SyncWorkRequest.kt create mode 100644 opensrp-core/src/main/java/org/smartregister/util/WorkerUtils.kt diff --git a/opensrp-core/build.gradle b/opensrp-core/build.gradle index 01c12289b..d74a5e969 100644 --- a/opensrp-core/build.gradle +++ b/opensrp-core/build.gradle @@ -236,11 +236,16 @@ dependencies { compileOnly 'com.google.firebase:firebase-crashlytics' compileOnly 'com.google.firebase:firebase-perf' - def work_version = "2.7.1" - implementation "androidx.work:work-runtime:$work_version" - // Add the dependency for the Performance Monitoring library + // WorkManager + def work_version = "2.7.1" + api "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/domain/db/EventClientQueryResult.java b/opensrp-core/src/main/java/org/smartregister/domain/db/EventClientQueryResult.java new file mode 100644 index 000000000..3f17d0b2c --- /dev/null +++ b/opensrp-core/src/main/java/org/smartregister/domain/db/EventClientQueryResult.java @@ -0,0 +1,32 @@ +package org.smartregister.domain.db; + +import androidx.annotation.NonNull; + +import java.util.List; + +public class EventClientQueryResult { + + private List eventClientList; + private int maxRowId; + + public EventClientQueryResult(int maxRowId, @NonNull List eventClients) { + this.maxRowId = maxRowId; + this.eventClientList = eventClients; + } + + public List getEventClientList() { + return eventClientList; + } + + public void setEventClientList(List eventClientList) { + this.eventClientList = eventClientList; + } + + public int getMaxRowId() { + return maxRowId; + } + + public void setMaxRowId(int maxRowId) { + this.maxRowId = maxRowId; + } +} diff --git a/opensrp-core/src/main/java/org/smartregister/repository/EventClientRepository.java b/opensrp-core/src/main/java/org/smartregister/repository/EventClientRepository.java index 987e387d5..d1924e927 100644 --- a/opensrp-core/src/main/java/org/smartregister/repository/EventClientRepository.java +++ b/opensrp-core/src/main/java/org/smartregister/repository/EventClientRepository.java @@ -37,8 +37,8 @@ import org.smartregister.domain.db.Column; import org.smartregister.domain.db.ColumnAttribute; import org.smartregister.domain.db.EventClient; +import org.smartregister.domain.db.EventClientQueryResult; import org.smartregister.p2p.sync.data.JsonData; -import org.smartregister.sync.intent.P2pProcessRecordsService; import org.smartregister.sync.intent.PullUniqueIdsIntentService; import org.smartregister.util.DatabaseMigrationUtils; import org.smartregister.util.JsonFormUtils; @@ -882,7 +882,7 @@ public List fetchEventClients(long startServerVersion, long lastSer new String[]{String.valueOf(startServerVersion), String.valueOf(lastServerVersion)}); } - public P2pProcessRecordsService.EventClientQueryResult fetchEventClientsByRowId( + public EventClientQueryResult fetchEventClientsByRowId( long lastProcessedRowId) { List list = new ArrayList<>(); Cursor cursor = null; @@ -925,7 +925,7 @@ public P2pProcessRecordsService.EventClientQueryResult fetchEventClientsByRowId( cursor.close(); } } - return new P2pProcessRecordsService.EventClientQueryResult(maxRowId, list); + return new EventClientQueryResult(maxRowId, list); } /** 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 000000000..ed04273e9 --- /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 db355f208..769719dce 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 dcedb1bed..3a8694570 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 4122ded38..b869a7deb 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 c4322cef8..566051bf2 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 f6ddccdc3..8a7aafdf9 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 de27e4cac..b15da8793 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/P2pProcessRecordsService.java b/opensrp-core/src/main/java/org/smartregister/sync/intent/P2pProcessRecordsService.java index 33451086d..95218baaf 100644 --- a/opensrp-core/src/main/java/org/smartregister/sync/intent/P2pProcessRecordsService.java +++ b/opensrp-core/src/main/java/org/smartregister/sync/intent/P2pProcessRecordsService.java @@ -1,13 +1,14 @@ package org.smartregister.sync.intent; import android.content.Intent; -import androidx.annotation.NonNull; + import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import org.smartregister.CoreLibrary; import org.smartregister.domain.FetchStatus; import org.smartregister.domain.db.EventClient; +import org.smartregister.domain.db.EventClientQueryResult; import org.smartregister.repository.AllSharedPreferences; import org.smartregister.repository.EventClientRepository; import org.smartregister.util.Utils; @@ -21,6 +22,7 @@ * Created by Ephraim Kigamba - ekigamba@ona.io on 10/05/2019 */ +@Deprecated public class P2pProcessRecordsService extends BaseSyncIntentService { /** @@ -57,12 +59,12 @@ protected void onHandleIntent(@Nullable Intent intent) { DrishtiApplication.getInstance().getClientProcessor().processClient(eventClientList); int tableMaxRowId = eventClientRepository.getMaxRowId(EventClientRepository.Table.event); - if (tableMaxRowId == eventClientQueryResult.maxRowId) { + if (tableMaxRowId == eventClientQueryResult.getMaxRowId()) { eventsMaxRowId = -1; allSharedPreferences.resetLastPeerToPeerSyncProcessedEvent(); } else { - eventsMaxRowId = eventClientQueryResult.maxRowId; - allSharedPreferences.setLastPeerToPeerSyncProcessedEvent(eventClientQueryResult.maxRowId); + eventsMaxRowId = eventClientQueryResult.getMaxRowId(); + allSharedPreferences.setLastPeerToPeerSyncProcessedEvent(eventClientQueryResult.getMaxRowId()); } // Profile images do not have a foreign key to the clients and can therefore be saved during the sync. @@ -87,33 +89,6 @@ protected void sendSyncStatusBroadcastMessage(FetchStatus fetchStatus) { CoreLibrary.getInstance().context().applicationContext().sendBroadcast(Utils.completeSync(fetchStatus)); } - public static class EventClientQueryResult { - - private List eventClientList; - private int maxRowId; - - public EventClientQueryResult(int maxRowId, @NonNull List eventClients) { - this.maxRowId = maxRowId; - this.eventClientList = eventClients; - } - - public List getEventClientList() { - return eventClientList; - } - - public void setEventClientList(List eventClientList) { - this.eventClientList = eventClientList; - } - - public int getMaxRowId() { - return maxRowId; - } - - public void setMaxRowId(int maxRowId) { - this.maxRowId = maxRowId; - } - } - @Override public void onDestroy() { // This ensure that even if the `onHandleIntent` is closed prematurely, we remove the Snackbar since 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 c1b837f4a..63d002ab6 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/PullUniqueIdsIntentService.java b/opensrp-core/src/main/java/org/smartregister/sync/intent/PullUniqueIdsIntentService.java index 06d7b1795..a53bb8559 100644 --- a/opensrp-core/src/main/java/org/smartregister/sync/intent/PullUniqueIdsIntentService.java +++ b/opensrp-core/src/main/java/org/smartregister/sync/intent/PullUniqueIdsIntentService.java @@ -21,6 +21,7 @@ import timber.log.Timber; +@Deprecated public class PullUniqueIdsIntentService extends BaseSyncIntentService { public static final String ID_URL = "/uniqueids/get"; public static final String IDENTIFIERS = "identifiers"; diff --git a/opensrp-core/src/main/java/org/smartregister/sync/intent/SettingsSyncIntentService.java b/opensrp-core/src/main/java/org/smartregister/sync/intent/SettingsSyncIntentService.java index 660470968..a64631ef7 100644 --- a/opensrp-core/src/main/java/org/smartregister/sync/intent/SettingsSyncIntentService.java +++ b/opensrp-core/src/main/java/org/smartregister/sync/intent/SettingsSyncIntentService.java @@ -17,6 +17,8 @@ /** * Created by ndegwamartin on 14/09/2018. */ + +@Deprecated public class SettingsSyncIntentService extends BaseSyncIntentService { public static final String SETTINGS_URL = "/rest/settings/sync"; 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 2a18d0123..d38a60307 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 294a1bb41..db449c8ff 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; @@ -57,6 +58,7 @@ import timber.log.Timber; +@Deprecated public class SyncIntentService extends BaseSyncIntentService { public static final String SYNC_URL = "/rest/event/sync"; protected static final int EVENT_PULL_LIMIT = 250; @@ -170,7 +172,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 +218,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 197c8b7db..2704b2e7e 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 987a069a0..c395d6627 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 87c217b5b..6a3385ff4 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 a91dafa15..220d1cd35 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 000000000..526674341 --- /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 000000000..035f6d83c --- /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 000000000..fadbd6d46 --- /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 000000000..7e66baf71 --- /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.SyncWorkRequest +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(){ + SyncWorkRequest.runWorker(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 000000000..decefc8d5 --- /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/P2pProcessRecordsWorker.kt b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/P2pProcessRecordsWorker.kt new file mode 100644 index 000000000..2015c8716 --- /dev/null +++ b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/P2pProcessRecordsWorker.kt @@ -0,0 +1,87 @@ +package org.smartregister.sync.wm.worker + +import android.content.Context +import androidx.work.WorkerParameters +import org.smartregister.CoreLibrary +import org.smartregister.domain.FetchStatus +import org.smartregister.repository.EventClientRepository +import org.smartregister.util.Utils +import org.smartregister.util.WorkerNotificationDelegate +import org.smartregister.view.activity.DrishtiApplication +import timber.log.Timber + +class P2pProcessRecordsWorker(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 openSrpContext = coreLibrary.context() + val allSharedPreferences = openSrpContext.allSharedPreferences() + if (allSharedPreferences.isPeerToPeerUnprocessedEvents){ + coreLibrary.isPeerToPeerProcessing = true + + var eventsMaxRowId = allSharedPreferences.lastPeerToPeerSyncProcessedEvent.toLong() + val eventClientRepository = openSrpContext.eventClientRepository + + while (eventsMaxRowId > -1) { + val eventClientQueryResult = + eventClientRepository.fetchEventClientsByRowId(eventsMaxRowId) + val eventClientList = eventClientQueryResult.eventClientList + if (eventClientList.size > 0) { + DrishtiApplication.getInstance().clientProcessor.processClient( + eventClientList + ) + val tableMaxRowId = + eventClientRepository.getMaxRowId(EventClientRepository.Table.event) + if (tableMaxRowId == eventClientQueryResult.maxRowId) { + eventsMaxRowId = -1 + allSharedPreferences.resetLastPeerToPeerSyncProcessedEvent() + } else { + eventsMaxRowId = eventClientQueryResult.maxRowId.toLong() + allSharedPreferences.lastPeerToPeerSyncProcessedEvent = + eventClientQueryResult.maxRowId + } + + // Profile images do not have a foreign key to the clients and can therefore be saved during the sync. + // They also do not take long to save and therefore happen during sync + Timber.i( + "Finished processing %s EventClients", + eventClientList.size.toString() + ) + } else { + allSharedPreferences.resetLastPeerToPeerSyncProcessedEvent() + break + } + } + + sendSyncStatusBroadcastMessage(FetchStatus.fetched) + } + + Result.success().apply { + notificationDelegate.notify("Success!!") + } + } catch (e: Exception) { + Timber.e(e) + Result.failure().apply { + notificationDelegate.notify("Error: ${e.message}") + } + } + + } + + fun sendSyncStatusBroadcastMessage(fetchStatus: FetchStatus) = applicationContext.sendBroadcast(Utils.completeSync(fetchStatus)) + + override fun onStopped() { + val coreLibrary = CoreLibrary.getInstance() + coreLibrary.isPeerToPeerProcessing = false + } + + companion object { + const val TAG = "P2pProcessRecordsWorker" + } +} \ No newline at end of file diff --git a/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/PlanPeriodicPlanEvaluationWorker.kt b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/PlanPeriodicPlanEvaluationWorker.kt new file mode 100644 index 000000000..baba2c057 --- /dev/null +++ b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/PlanPeriodicPlanEvaluationWorker.kt @@ -0,0 +1,108 @@ +package org.smartregister.sync.wm.worker + +import android.content.Context +import android.text.TextUtils +import androidx.work.WorkerParameters +import org.joda.time.DateTime +import org.smartregister.AllConstants +import org.smartregister.CoreLibrary +import org.smartregister.domain.Action +import org.smartregister.domain.Jurisdiction +import org.smartregister.job.PlanPeriodicEvaluationJob +import org.smartregister.pathevaluator.TriggerType +import org.smartregister.pathevaluator.plan.PlanEvaluator +import org.smartregister.sync.helper.PeriodicTriggerEvaluationHelper +import org.smartregister.sync.wm.workerrequest.SyncWorkRequest +import org.smartregister.util.WorkerNotificationDelegate +import timber.log.Timber + +class PlanPeriodicPlanEvaluationWorker(context: Context, workerParams: WorkerParameters) : + BaseWorker(context, workerParams) { + private val notificationDelegate = WorkerNotificationDelegate(context, TAG) + + override fun doWork(): Result { + beforeWork() + + notificationDelegate.notify("Running \u8086") + return try { + if (inputData.keyValueMap.isNotEmpty()){ + val planId = inputData.getString(AllConstants.INTENT_KEY.PLAN_ID) + val actionIdentifier = + inputData.getString(AllConstants.INTENT_KEY.ACTION_IDENTIFIER) + val actionCode = inputData.getString(AllConstants.INTENT_KEY.ACTION_CODE) + val actionJsonString = inputData.getString(AllConstants.INTENT_KEY.ACTION) + + if (TextUtils.isEmpty(planId) || TextUtils.isEmpty(actionJsonString) + || TextUtils.isEmpty(actionIdentifier) || TextUtils.isEmpty(actionCode) + ) { + Timber.e( + Exception(), + "Periodic action was not evaluated since planId, action, action-identifier OR action-code was empty" + ) + return Result.failure() + } + + val action = PlanPeriodicEvaluationJob.gson.fromJson( + actionJsonString, + Action::class.java + ) + if (action == null){ + Timber.e( + Exception(), + "An error occurred and the service did not evaluate the plan/action" + ) + return Result.failure() + } + + val planDefinitionRepository = CoreLibrary.getInstance().context() + .planDefinitionRepository + val planDefinition = planDefinitionRepository.findPlanDefinitionById(planId) + + + val timeNow = DateTime.now() + if (planDefinition.effectivePeriod != null && planDefinition.effectivePeriod.end.isBefore(timeNow) + || action.timingPeriod != null && action.timingPeriod.end.isBefore(timeNow) + ) { + SyncWorkRequest.cancelWork(applicationContext, this::class.java, workName = generateWorkName(actionIdentifier!!, actionCode!!)) + + PeriodicTriggerEvaluationHelper().cancelJobsForAction( // TODO: Test cancel all previous/queued work requests + actionIdentifier, + actionCode + ) + } else { + val allSharedPreferences = + CoreLibrary.getInstance().context().allSharedPreferences() + val planEvaluator = PlanEvaluator(allSharedPreferences.fetchRegisteredANM()) + val jurisdiction = Jurisdiction( + allSharedPreferences.fetchDefaultLocalityId(allSharedPreferences.fetchRegisteredANM()) + ) + + // TODO: Change this to evaluate a single action + planEvaluator.evaluatePlanAction( + planDefinition, + TriggerType.PERIODIC, + jurisdiction, + null, + action + ) + } + } + + Result.success().apply { + notificationDelegate.notify("Success!!") + } + } catch (e: Exception) { + Timber.e(e) + Result.failure().apply { + notificationDelegate.notify("Error: ${e.message}") + } + } + + } + + fun generateWorkName(actionIdentifier: String, actionCode: String) = "$TAG-$actionCode-$actionIdentifier" + + companion object { + const val TAG = "PlanPeriodicPlanEvaluationWorker" + } +} \ 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 000000000..41c7b8869 --- /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/PullUniqueIdsWorker.kt b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/PullUniqueIdsWorker.kt new file mode 100644 index 000000000..6ef7e166f --- /dev/null +++ b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/PullUniqueIdsWorker.kt @@ -0,0 +1,84 @@ +package org.smartregister.sync.wm.worker + +import android.content.Context +import androidx.work.WorkerParameters +import org.json.JSONObject +import org.smartregister.CoreLibrary +import org.smartregister.exception.NoHttpResponseException +import org.smartregister.repository.UniqueIdRepository +import org.smartregister.service.HTTPAgent +import org.smartregister.sync.intent.PullUniqueIdsIntentService +import org.smartregister.util.WorkerNotificationDelegate +import timber.log.Timber + +class PullUniqueIdsWorker(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() + val configs = openSrpContext.syncConfiguration + val uniqueIdRepo = openSrpContext.context().uniqueIdRepository + + val numberToGenerate: Int = + if (uniqueIdRepo.countUnUsedIds() == 0L) { // first time pull no ids at all + configs!!.uniqueIdInitialBatchSize + } else if (uniqueIdRepo.countUnUsedIds() <= 250) { //maintain a minimum of 250 else skip this pull + configs!!.uniqueIdBatchSize + } else { + return Result.failure() + } + val ids: JSONObject = fetchOpenMRSIds(openSrpContext.context().httpAgent, configs.uniqueIdSource, numberToGenerate) + if (ids.has(PullUniqueIdsIntentService.IDENTIFIERS)) { + parseResponse(uniqueIdRepo, ids) + } + + Result.success().apply { + notificationDelegate.notify("Success!!") + } + } catch (e: Exception) { + Timber.e(e) + Result.failure().apply { + notificationDelegate.notify("Error: ${e.message}") + } + } + + } + + @Throws(Exception::class) + private fun fetchOpenMRSIds(httpAgent: HTTPAgent, source: Int, numberToGenerate: Int): JSONObject { + var baseUrl = CoreLibrary.getInstance().context().configuration().dristhiBaseURL() + val endString = "/" + if (baseUrl.endsWith(endString)) { + baseUrl = baseUrl.substring(0, baseUrl.lastIndexOf(endString)) + } + val url = + baseUrl + PullUniqueIdsIntentService.ID_URL + "?source=" + source + "&numberToGenerate=" + numberToGenerate + Timber.i("URL: %s", url) + val resp = httpAgent.fetch(url) + if (resp.isFailure) { + throw NoHttpResponseException(PullUniqueIdsIntentService.ID_URL + " not returned data") + } + return JSONObject(resp.payload() as String) + } + + @Throws(Exception::class) + private fun parseResponse(uniqueIdRepo: UniqueIdRepository, idsFromOMRS: JSONObject) { + val jsonArray = idsFromOMRS.getJSONArray(PullUniqueIdsIntentService.IDENTIFIERS) + if (jsonArray.length() > 0) { + val ids: MutableList = ArrayList() + for (i in 0 until jsonArray.length()) { + ids.add(jsonArray.getString(i)) + } + uniqueIdRepo.bulkInsertOpenmrsIds(ids) + } + } + + companion object { + const val TAG = "PullUniqueIdsWorker" + } +} \ No newline at end of file diff --git a/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SettingsSyncWorker.kt b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SettingsSyncWorker.kt new file mode 100644 index 000000000..9fec42197 --- /dev/null +++ b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SettingsSyncWorker.kt @@ -0,0 +1,56 @@ +package org.smartregister.sync.wm.worker + +import android.content.Context +import androidx.work.WorkerParameters +import org.json.JSONException +import org.smartregister.CoreLibrary +import org.smartregister.job.SyncServiceJob +import org.smartregister.sync.helper.SyncSettingsServiceHelper +import org.smartregister.sync.wm.workerrequest.SyncWorkRequest +import org.smartregister.util.WorkerNotificationDelegate +import timber.log.Timber + +class SettingsSyncWorker(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 syncSettingsServiceHelper = SyncSettingsServiceHelper(openSrpContext.configuration().dristhiBaseURL(), openSrpContext.httpAgent) + val isSuccessfulSync = processSettings(syncSettingsServiceHelper) + if (isSuccessfulSync) { + SyncWorkRequest.runWorker(applicationContext, SyncWorker::class.java) + } + + Result.success().apply { + notificationDelegate.notify("Success!!") + } + } catch (e: Exception) { + Timber.e(e) + Result.failure().apply { + notificationDelegate.notify("Error: ${e.message}") + } + } + + } + + fun processSettings(syncSettingsServiceHelper: SyncSettingsServiceHelper): Boolean { + Timber.d("In Settings Sync Intent Service...") + var isSuccessfulSync = true + try { + val count: Int = syncSettingsServiceHelper.processIntent() + } catch (e: JSONException) { + isSuccessfulSync = false + Timber.e(" Error fetching client settings") + } + return isSuccessfulSync + } + + companion object { + const val TAG = "SettingsSyncWorker" + } +} \ 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 000000000..ca21ad8c0 --- /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 000000000..da4aa7842 --- /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 000000000..1bb5a3e0e --- /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 000000000..47ab04c42 --- /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/SyncWorker.kt b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SyncWorker.kt new file mode 100644 index 000000000..e0ba4d75d --- /dev/null +++ b/opensrp-core/src/main/java/org/smartregister/sync/wm/worker/SyncWorker.kt @@ -0,0 +1,550 @@ +package org.smartregister.sync.wm.worker + +import android.content.Context +import android.content.Intent +import android.util.Pair +import androidx.annotation.IntRange +import androidx.localbroadcastmanager.content.LocalBroadcastManager +import androidx.work.WorkerParameters +import com.google.firebase.perf.metrics.Trace +import org.apache.commons.lang3.StringUtils +import org.joda.time.DateTime +import org.json.JSONException +import org.json.JSONObject +import org.smartregister.AllConstants +import org.smartregister.AllConstants.PerformanceMonitoring +import org.smartregister.CoreLibrary +import org.smartregister.R +import org.smartregister.SyncConfiguration +import org.smartregister.domain.FetchStatus +import org.smartregister.domain.Response +import org.smartregister.domain.SyncEntity +import org.smartregister.domain.SyncProgress +import org.smartregister.receiver.SyncStatusBroadcastReceiver +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 +import org.smartregister.util.PerformanceMonitoringUtils +import org.smartregister.util.SyncUtils +import org.smartregister.util.Utils +import org.smartregister.util.WorkerNotificationDelegate +import org.smartregister.view.activity.DrishtiApplication +import timber.log.Timber +import java.text.MessageFormat +import java.util.Date + +class SyncWorker(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 syncUtils = SyncUtils(applicationContext) + val httpAgent = CoreLibrary.getInstance().context().httpAgent + val eventSyncTrace: Trace = + PerformanceMonitoringUtils.initTrace(PerformanceMonitoring.EVENT_SYNC) + val processClientTrace: Trace = + PerformanceMonitoringUtils.initTrace(PerformanceMonitoring.CLIENT_PROCESSING) + val allSharedPreferences: AllSharedPreferences = CoreLibrary.getInstance().context().allSharedPreferences() + + Syncer(applicationContext, httpAgent, syncUtils, eventSyncTrace, processClientTrace, allSharedPreferences) + .apply { + doSync() + } + + 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 = "SyncWorker" + } +} + +class Syncer( + private val context: Context, + private val httpAgent: HTTPAgent, + private val syncUtils: SyncUtils, + private val eventSyncTrace: Trace, + private val processClientTrace: Trace, + allSharedPreferences: AllSharedPreferences +) { + private val validateAssignmentHelper: ValidateAssignmentHelper + private var totalRecords: Long = 0 + private var fetchedRecords = 0 + private var totalRecordsCount = 0 + private val team: String? + private val providerId: String? + + //this variable using to track the sync request goes along with add events/clients + private var isEmptyToAdd = true + + init { + providerId = allSharedPreferences.fetchRegisteredANM() + team = allSharedPreferences.fetchDefaultTeam(providerId) + validateAssignmentHelper = ValidateAssignmentHelper(syncUtils) + } + + private fun sendSyncStatusBroadcastMessage(fetchStatus: FetchStatus) { + val intent = Intent() + intent.action = SyncStatusBroadcastReceiver.ACTION_SYNC_STATUS + intent.putExtra(SyncStatusBroadcastReceiver.EXTRA_FETCH_STATUS, fetchStatus) + context.sendBroadcast(intent) + } + + fun doSync() { + sendSyncStatusBroadcastMessage(FetchStatus.fetchStarted) + + if (!NetworkUtils.isNetworkAvailable()) { + complete(FetchStatus.noConnection) + return + } + try { + val hasValidAuthorization: Boolean = syncUtils.verifyAuthorization() + var isSuccessfulPushSync = false + if (hasValidAuthorization || !CoreLibrary.getInstance().syncConfiguration!!.disableSyncToServerIfUserIsDisabled()) { + isSuccessfulPushSync = pushToServer() + } + if (!hasValidAuthorization) { + syncUtils.logoutUser() + } else if (!syncUtils.isAppVersionAllowed) { + if (isSuccessfulPushSync) { + syncUtils.logoutUser() + } else { + return + } + } else { + pullECFromServer() + } + } catch (e: Exception) { + Timber.e(e) + complete(FetchStatus.fetchedFailed) + } + } + + fun pullECFromServer() { + fetchRetry(0, true) + } + + @Synchronized + private fun fetchRetry(count: Int, returnCount: Boolean) { + try { + val configs = CoreLibrary.getInstance().syncConfiguration + if (configs!!.syncFilterParam == null || StringUtils.isBlank( + configs.syncFilterValue + ) + ) { + complete(FetchStatus.fetchedFailed) + return + } + val ecSyncUpdater = ECSyncHelper.getInstance(context) + val baseUrl = getFormattedBaseUrl() + val lastSyncDatetime = ecSyncUpdater.lastSyncTimeStamp + Timber.i("LAST SYNC DT %s", DateTime(lastSyncDatetime)) + complete(FetchStatus.fetchedFailed) + startEventTrace(PerformanceMonitoring.FETCH, 0) + val syncParamBuilder = RequestParamsBuilder().configureSyncFilter( + configs.syncFilterParam.value(), configs.syncFilterValue + ).addServerVersion(lastSyncDatetime).addEventPullLimit(getEventPullLimit()) + val resp = getUrlResponse( + httpAgent, + baseUrl + SYNC_URL, syncParamBuilder, + configs, returnCount + ) + if (resp == null) { + FetchStatus.fetchedFailed.setDisplayValue("Empty response") + complete(FetchStatus.fetchedFailed) + return + } + if (resp.isUrlError) { + FetchStatus.fetchedFailed.setDisplayValue(resp.status().displayValue()) + complete(FetchStatus.fetchedFailed) + return + } + if (resp.isTimeoutError) { + FetchStatus.fetchedFailed.setDisplayValue(resp.status().displayValue()) + complete(FetchStatus.fetchedFailed) + return + } + if (resp.isFailure && !resp.isUrlError && !resp.isTimeoutError) { + fetchFailed(count) + return + } + if (returnCount) { + totalRecords = resp.totalRecords + } + processFetchedEvents(httpAgent, resp, ecSyncUpdater, count) + } catch (e: java.lang.Exception) { + Timber.e(e, "Fetch Retry Exception: %s", e.message) + fetchFailed(count) + } + } + + /** + * This methods makes a request to the server using either Get or Post as is configured by [org.smartregister.SyncConfiguration.isSyncUsingPost] + * + * @param baseURL the base url for the request + * @param requestParamsBuilder the query string builder object + * @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) + */ + fun getUrlResponse( + httpAgent: HTTPAgent, + baseURL: String, + requestParamsBuilder: RequestParamsBuilder, + configs: SyncConfiguration, + returnCount: Boolean + ): Response<*> { + val response: Response<*> + var requestUrl = baseURL + if (configs.isSyncUsingPost) { + response = httpAgent.postWithJsonResponse( + requestUrl, + requestParamsBuilder.returnCount(returnCount).build() + ) + } else { + requestUrl += "?" + requestParamsBuilder.build() + Timber.i("URL: %s", requestUrl) + response = httpAgent.fetch(requestUrl) + } + return response + } + + @Throws(JSONException::class) + private fun processFetchedEvents( + httpAgent: HTTPAgent, + resp: Response<*>, + ecSyncUpdater: ECSyncHelper, + count: Int + ) { + val eCount: Int + var jsonObject = JSONObject() + if (resp.payload() == null) { + eCount = 0 + } else { + jsonObject = JSONObject(resp.payload() as String) + eCount = fetchNumberOfEvents(jsonObject) + Timber.i("Parse Network Event Count: %s", eCount) + } + if (eCount == 0) { + complete(FetchStatus.nothingFetched) + sendSyncProgressBroadcast(eCount) // Complete progress update + } else if (eCount < 0) { + fetchFailed(count) + } else { + val serverVersionPair = getMinMaxServerVersions(jsonObject) + var lastServerVersion = serverVersionPair.second - 1 + if (eCount < getEventPullLimit()) { + lastServerVersion = serverVersionPair.second + } + PerformanceMonitoringUtils.addAttribute( + eventSyncTrace, + AllConstants.COUNT, + eCount.toString() + ) + PerformanceMonitoringUtils.stopTrace(eventSyncTrace) + val isSaved = ecSyncUpdater.saveAllClientsAndEvents(jsonObject) + //update sync time if all event client is save. + if (isSaved) { + PerformanceMonitoringUtils.startTrace(processClientTrace) + processClient(serverVersionPair) + PerformanceMonitoringUtils.addAttribute( + processClientTrace, + AllConstants.COUNT, + eCount.toString() + ) + PerformanceMonitoringUtils.addAttribute( + processClientTrace, + PerformanceMonitoring.TEAM, + team + ) + PerformanceMonitoringUtils.stopTrace(processClientTrace) + ecSyncUpdater.updateLastSyncTimeStamp(lastServerVersion) + } + sendSyncProgressBroadcast(eCount) + fetchRetry(0, true) + } + } + + private fun fetchFailed(count: Int) { + if (count < CoreLibrary.getInstance().syncConfiguration!!.syncMaxRetries) { + val newCount = count + 1 + fetchRetry(newCount, false) + } else { + complete(FetchStatus.fetchedFailed) + } + } + + private fun processClient(serverVersionPair: Pair) { + try { + val ecUpdater = ECSyncHelper.getInstance(context) + val events = + ecUpdater.allEventClients(serverVersionPair.first - 1, serverVersionPair.second) + DrishtiApplication.getInstance().clientProcessor.processClient( + events + ) + sendSyncStatusBroadcastMessage(FetchStatus.fetched) + } catch (e: java.lang.Exception) { + Timber.e(e, "Process Client Exception: %s", e.message) + } + } + + // PUSH TO SERVER + private fun pushToServer(): Boolean { + return pushECToServer( + CoreLibrary.getInstance().context().eventClientRepository + ) && + (!CoreLibrary.getInstance().context().hasForeignEvents() || pushECToServer( + CoreLibrary.getInstance().context().foreignEventClientRepository + )) + } + + private fun pushECToServer(db: EventClientRepository): Boolean { + var isSuccessfulPushSync = true + isEmptyToAdd = true + // push foreign events to server + val totalEventCount = db.unSyncedEventsCount + var eventsUploadedCount = 0 + var baseUrl = CoreLibrary.getInstance().context().configuration().dristhiBaseURL() + if (baseUrl.endsWith(context.getString(R.string.url_separator))) { + baseUrl = + baseUrl.substring(0, baseUrl.lastIndexOf(context.getString(R.string.url_separator))) + } + for (i in 0 until syncUtils.getNumOfSyncAttempts()) { + val pendingEventsClients = db.getUnSyncedEvents( + getEventBatchSize()!! + ) + if (pendingEventsClients.isEmpty()) { + break + } + // create request body + val request = JSONObject() + try { + if (pendingEventsClients.containsKey(AllConstants.KEY.CLIENTS)) { + val value = pendingEventsClients[AllConstants.KEY.CLIENTS] + request.put(AllConstants.KEY.CLIENTS, value) + if (value is List<*>) { + eventsUploadedCount += value.size + } + } + if (pendingEventsClients.containsKey(AllConstants.KEY.EVENTS)) { + request.put( + AllConstants.KEY.EVENTS, + pendingEventsClients[AllConstants.KEY.EVENTS] + ) + } + } catch (e: JSONException) { + Timber.e(e) + } + isEmptyToAdd = false + val jsonPayload = request.toString() + startEventTrace(PerformanceMonitoring.PUSH, eventsUploadedCount) + val response: Response = httpAgent.post( + MessageFormat.format( + "{0}/{1}", + baseUrl, + ADD_URL + ), + jsonPayload + ) + if (response.isFailure) { + Timber.e("Events sync failed.") + isSuccessfulPushSync = false + } else { + // do not mark items in list of failed events/clients as synced + var failedClients: Set? = null + var failedEvents: Set? = null + val responseData = response.payload() + if (StringUtils.isNotEmpty(responseData)) { + try { + val failedEventClients = JSONObject(responseData) + failedClients = + getFailed(FAILED_CLIENTS, failedEventClients) + failedEvents = + getFailed(FAILED_EVENTS, failedEventClients) + } catch (e: JSONException) { + Timber.e(e) + } + } + db.markEventsAsSynced(pendingEventsClients, failedEvents, failedClients) + Timber.i("Events synced successfully.") + PerformanceMonitoringUtils.stopTrace(eventSyncTrace) + updateProgress(eventsUploadedCount, totalEventCount) + if (totalEventCount - eventsUploadedCount > 0) pushECToServer(db) + break + } + } + return isSuccessfulPushSync + } + + private fun getFailed(recordType: String, failedEventClients: JSONObject): Set? { + var set: MutableSet? = null + try { + val failed = failedEventClients.getJSONArray(recordType) + if (failed.length() > 0) { + set = HashSet() + for (i in 0 until failed.length()) { + set.add(failed.getString(i)) + } + } + } catch (e: JSONException) { + Timber.e(e) + } + return set + } + + private fun startEventTrace(action: String, count: Int) { + val configs = CoreLibrary.getInstance().syncConfiguration + if (configs!!.firebasePerformanceMonitoringEnabled()) { + PerformanceMonitoringUtils.clearTraceAttributes(eventSyncTrace) + PerformanceMonitoringUtils.addAttribute( + eventSyncTrace, + PerformanceMonitoring.TEAM, + team + ) + PerformanceMonitoringUtils.addAttribute( + eventSyncTrace, + PerformanceMonitoring.ACTION, + action + ) + PerformanceMonitoringUtils.addAttribute( + eventSyncTrace, + AllConstants.COUNT, + count.toString() + ) + PerformanceMonitoringUtils.startTrace(eventSyncTrace) + } + } + + fun isEmptyToAdd(): Boolean { + return isEmptyToAdd + } + + fun complete(fetchStatus: FetchStatus) { + val intent = Intent() + intent.action = SyncStatusBroadcastReceiver.ACTION_SYNC_STATUS + intent.putExtra(SyncStatusBroadcastReceiver.EXTRA_FETCH_STATUS, fetchStatus) + intent.putExtra(SyncStatusBroadcastReceiver.EXTRA_COMPLETE_STATUS, true) + context.sendBroadcast(intent) + + //sync time not update if sync is fail + if (fetchStatus != FetchStatus.noConnection && fetchStatus != FetchStatus.fetchedFailed) { + val ecSyncUpdater = ECSyncHelper.getInstance(context) + ecSyncUpdater.updateLastCheckTimeStamp(Date().time) + if (CoreLibrary.getInstance().syncConfiguration!!.validateUserAssignments()) { + validateAssignmentHelper.validateUserAssignment() + } + } + } + + private fun updateProgress( + @IntRange(from = 0) progress: Int, + @IntRange(from = 1) total: Int + ) { + val uploadProgressStatus = FetchStatus.fetchProgress + uploadProgressStatus.setDisplayValue( + String.format( + context.getString(R.string.sync_upload_progress_float), + progress * 100 / total + ) + ) + sendSyncStatusBroadcastMessage(uploadProgressStatus) + } + + private fun getMinMaxServerVersions(jsonObject: JSONObject?): Pair { + val EVENTS = "events" + try { + if (jsonObject != null && jsonObject.has(EVENTS)) { + val events = jsonObject.getJSONArray(EVENTS) + var maxServerVersion = Long.MIN_VALUE + var minServerVersion = Long.MAX_VALUE + for (i in 0 until events.length()) { + val o = events[i] + if (o is JSONObject) { + val jo = o + if (jo.has(AllConstants.SERVER_VERSION)) { + val serverVersion = jo.getLong(AllConstants.SERVER_VERSION) + if (serverVersion > maxServerVersion) { + maxServerVersion = serverVersion + } + if (serverVersion < minServerVersion) { + minServerVersion = serverVersion + } + } + } + } + return Pair.create(minServerVersion, maxServerVersion) + } + } catch (e: java.lang.Exception) { + Timber.e(e) + } + return Pair.create(0L, 0L) + } + + fun fetchNumberOfEvents(jsonObject: JSONObject?): Int { + var count = -1 + val NO_OF_EVENTS = "no_of_events" + try { + if (jsonObject != null && jsonObject.has(NO_OF_EVENTS)) { + count = jsonObject.getInt(NO_OF_EVENTS) + } + } catch (e: JSONException) { + Timber.e(e) + } + return count + } + + fun sendSyncProgressBroadcast(eventCount: Int) { + totalRecordsCount += totalRecords.toInt() + fetchedRecords = fetchedRecords + eventCount + val syncProgress = SyncProgress() + syncProgress.syncEntity = SyncEntity.EVENTS + syncProgress.totalRecords = totalRecords + syncProgress.percentageSynced = + Utils.calculatePercentage(totalRecordsCount.toLong(), fetchedRecords.toLong()) + val intent = Intent() + intent.action = AllConstants.SyncProgressConstants.ACTION_SYNC_PROGRESS + intent.putExtra(AllConstants.SyncProgressConstants.SYNC_PROGRESS_DATA, syncProgress) + LocalBroadcastManager.getInstance(context).sendBroadcast(intent) + } + + private fun getEventPullLimit(): Int { + return EVENT_PULL_LIMIT + } + + private fun getFormattedBaseUrl(): String { + var baseUrl = CoreLibrary.getInstance().context().configuration().dristhiBaseURL() + if (baseUrl.endsWith("/")) { + baseUrl = baseUrl.substring(0, baseUrl.lastIndexOf("/")) + } + return baseUrl + } + + private fun getEventBatchSize(): Int { + return EVENT_PUSH_LIMIT + } + + companion object { + const val SYNC_URL = "/rest/event/sync" + const val EVENT_PULL_LIMIT = 250 + const val EVENT_PUSH_LIMIT = 50 + private const val ADD_URL = "rest/event/add" + private const val FAILED_CLIENTS = "failed_clients" + private const val FAILED_EVENTS = "failed_events" + } +} \ 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 000000000..61dc5eb7b --- /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/SyncWorkRequest.kt b/opensrp-core/src/main/java/org/smartregister/sync/wm/workerrequest/SyncWorkRequest.kt new file mode 100644 index 000000000..03716cd3b --- /dev/null +++ b/opensrp-core/src/main/java/org/smartregister/sync/wm/workerrequest/SyncWorkRequest.kt @@ -0,0 +1,44 @@ +package org.smartregister.sync.wm.workerrequest + +import android.content.Context +import android.os.Build +import androidx.work.BackoffPolicy +import androidx.work.Constraints +import androidx.work.Data +import androidx.work.ExistingWorkPolicy +import androidx.work.ListenableWorker +import androidx.work.NetworkType +import androidx.work.OneTimeWorkRequest +import androidx.work.WorkManager +import timber.log.Timber +import java.util.concurrent.TimeUnit + + +object SyncWorkRequest { + + fun runWorker(context: Context, workerClass: Class, workName: String = workerClass::class.java.name, inputs: Data = Data.EMPTY) { + val inputData: Data = Data.Builder() + .putAll(inputs) + .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) + .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 2L, TimeUnit.MINUTES) + .build() + Timber.d("Scheduling job with name $workName immediately with JOB ID ${request.id}") + WorkManager.getInstance(context).enqueueUniqueWork(workName, ExistingWorkPolicy.REPLACE, request) + } + + fun cancelWork(context: Context, workerClass: Class, workName: String = workerClass::class.java.name){ + WorkManager.getInstance(context).cancelUniqueWork(workName) + } + +} \ 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 000000000..ed0161515 --- /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/repository/EventClientRepositoryTest.java b/opensrp-core/src/test/java/org/smartregister/repository/EventClientRepositoryTest.java index 48c25c536..f7e678872 100644 --- a/opensrp-core/src/test/java/org/smartregister/repository/EventClientRepositoryTest.java +++ b/opensrp-core/src/test/java/org/smartregister/repository/EventClientRepositoryTest.java @@ -29,7 +29,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.powermock.reflect.Whitebox; @@ -42,9 +41,9 @@ import org.smartregister.domain.db.Column; import org.smartregister.domain.db.ColumnAttribute; import org.smartregister.domain.db.EventClient; +import org.smartregister.domain.db.EventClientQueryResult; import org.smartregister.p2p.sync.data.JsonData; import org.smartregister.sync.ClientData; -import org.smartregister.sync.intent.P2pProcessRecordsService; import org.smartregister.view.activity.DrishtiApplication; import java.util.ArrayList; @@ -350,7 +349,7 @@ public void fetchEventClientsByRowId() throws Exception { } Mockito.when(sqliteDatabase.rawQuery(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.any(Object[].class))).thenReturn(matrixCursor); - P2pProcessRecordsService.EventClientQueryResult eventClientQueryResult = eventClientRepository.fetchEventClientsByRowId(0); + EventClientQueryResult eventClientQueryResult = eventClientRepository.fetchEventClientsByRowId(0); Assert.assertEquals(eventArray.length(), eventClientQueryResult.getMaxRowId()); } diff --git a/opensrp-core/src/test/java/org/smartregister/sync/intent/P2pProcessRecordsServiceTest.java b/opensrp-core/src/test/java/org/smartregister/sync/intent/P2pProcessRecordsServiceTest.java index cd2c27cc9..8a6c79b47 100644 --- a/opensrp-core/src/test/java/org/smartregister/sync/intent/P2pProcessRecordsServiceTest.java +++ b/opensrp-core/src/test/java/org/smartregister/sync/intent/P2pProcessRecordsServiceTest.java @@ -14,6 +14,7 @@ import org.smartregister.TestP2pApplication; import org.smartregister.domain.FetchStatus; import org.smartregister.domain.db.EventClient; +import org.smartregister.domain.db.EventClientQueryResult; import org.smartregister.repository.AllSharedPreferences; import org.smartregister.repository.EventClientRepository; import org.smartregister.sync.ClientProcessorForJava; @@ -65,7 +66,7 @@ public void onHandleIntentShouldNotProcessEventsIfPeerToPeerUnprocessedEventsRet CoreLibrary.getInstance().context().allSharedPreferences().setLastPeerToPeerSyncProcessedEvent(maxEventClientRowId); List eventClientList = new ArrayList<>(); eventClientList.add(new EventClient(null, null)); - Mockito.doReturn(new P2pProcessRecordsService.EventClientQueryResult(maxEventClientRowId, eventClientList)).when(eventClientRepository).fetchEventClientsByRowId(maxEventClientRowId); + Mockito.doReturn(new EventClientQueryResult(maxEventClientRowId, eventClientList)).when(eventClientRepository).fetchEventClientsByRowId(maxEventClientRowId); Mockito.doReturn(maxEventClientRowId).when(eventClientRepository).getMaxRowId(EventClientRepository.Table.event); Mockito.doNothing().when(clientProcessorForJava).processClient(eventClientList); 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 74af51fbd..d2fce52a6 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 07f052acc..c60551b29 100644 --- a/sample/src/main/java/org/smartregister/sample/MainActivity.java +++ b/sample/src/main/java/org/smartregister/sample/MainActivity.java @@ -26,6 +26,8 @@ import org.smartregister.cryptography.CryptographicHelper; import org.smartregister.cursoradapter.SmartRegisterQueryBuilder; import org.smartregister.sample.fragment.ReportFragment; +import org.smartregister.sync.wm.worker.SyncAllLocationsWorker; +import org.smartregister.sync.wm.workerrequest.SyncWorkRequest; import org.smartregister.util.AppHealthUtils; import org.smartregister.util.DateUtil; import org.smartregister.util.LangUtils; @@ -152,6 +154,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); + SyncWorkRequest.INSTANCE + .runWorker(getApplicationContext(), SyncAllLocationsWorker.class, SyncAllLocationsWorker.class.getName(), androidx.work.Data.EMPTY); + }); // File encryption example section ToggleButton toggle = (ToggleButton) findViewById(R.id.encrypt_decrypt_toggle); diff --git a/sample/src/main/res/layout-v17/content_main.xml b/sample/src/main/res/layout-v17/content_main.xml index e371d7703..c3f22a862 100644 --- a/sample/src/main/res/layout-v17/content_main.xml +++ b/sample/src/main/res/layout-v17/content_main.xml @@ -116,4 +116,12 @@ android:layout_marginLeft="100dp"/> +