/*
* Copyright (C) 2013-2017 Stichting Akvo (Akvo Foundation)
*
* This file is part of Akvo Flow.
*
* Akvo Flow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Akvo Flow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Akvo Flow. If not, see <http://www.gnu.org/licenses/>.
*/
package org.akvo.flow.service;
import android.app.IntentService;
import android.content.Intent;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.util.Pair;
import org.akvo.flow.R;
import org.akvo.flow.api.FlowApi;
import org.akvo.flow.data.database.SurveyDbAdapter;
import org.akvo.flow.domain.SurveyGroup;
import org.akvo.flow.domain.SurveyInstance;
import org.akvo.flow.domain.SurveyedLocale;
import org.akvo.flow.exception.HttpException;
import org.akvo.flow.util.ConstantUtil;
import org.akvo.flow.util.NotificationHelper;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import timber.log.Timber;
public class SurveyedDataPointSyncService extends IntentService {
private static final String TAG = SurveyedDataPointSyncService.class.getSimpleName();
public static final String SURVEY_GROUP = "survey_group";
private final Handler mHandler = new Handler();
public SurveyedDataPointSyncService() {
super(TAG);
// Tell the system to restart the service if it was unexpectedly stopped before completion
setIntentRedelivery(true);
}
@Override
protected void onHandleIntent(Intent intent) {
final long surveyGroupId = intent.getLongExtra(SURVEY_GROUP, SurveyGroup.ID_NONE);
int syncedRecords = 0;
FlowApi api = new FlowApi(getApplicationContext());
SurveyDbAdapter database = new SurveyDbAdapter(getApplicationContext()).open();
boolean correctSync = true;
NotificationHelper
.displayNotificationWithProgress(this, getString(R.string.syncing_records),
getString(R.string.pleasewait), true, true,
ConstantUtil.NOTIFICATION_RECORD_SYNC);
try {
Set<String> batch, lastBatch = new HashSet<>();
while (true) {
Pair<Set<String>, Boolean> syncResult = sync(database, api, surveyGroupId);
batch = syncResult.first;
if (!syncResult.second) {
//at least one of the data points seems corrupted
correctSync = syncResult.second;
}
batch.removeAll(lastBatch);// Remove duplicates.
if (batch.isEmpty()) {
break;
}
syncedRecords += batch.size();
sendBroadcastNotification();// Keep the UI fresh!
NotificationHelper
.displayNotificationWithProgress(this, getString(R.string.syncing_records),
String.format(getString(R.string.synced_records),
syncedRecords), true, true,
ConstantUtil.NOTIFICATION_RECORD_SYNC);
lastBatch = batch;
}
if (correctSync) {
NotificationHelper
.displayNotificationWithProgress(this, getString(R.string.syncing_records),
String.format(getString(R.string.synced_records),
syncedRecords), false, false,
ConstantUtil.NOTIFICATION_RECORD_SYNC);
} else {
NotificationHelper.displayErrorNotificationWithProgress(this,
getString(R.string.sync_error),
getString(R.string.syncing_corrupted_data_points_error), false, false,
ConstantUtil.NOTIFICATION_RECORD_SYNC);
}
} catch (HttpException e) {
String message = e.getMessage();
switch (e.getStatus()) {
case HttpURLConnection.HTTP_FORBIDDEN:
// A missing assignment might be the issue. Let's hint the user.
message = getString(R.string.error_assignment_text);
Timber.w(e, e.getMessage());
break;
default:
Timber.e(e, e.getMessage());
break;
}
displayToast(message);
NotificationHelper
.displayErrorNotificationWithProgress(this, getString(R.string.sync_error),
message, false,
false, ConstantUtil.NOTIFICATION_RECORD_SYNC);
} catch (IOException e) {
Timber.e(e, e.getMessage());
displayToast(getString(R.string.network_error));
NotificationHelper
.displayErrorNotificationWithProgress(this, getString(R.string.sync_error),
getString(R.string.network_error), false, false,
ConstantUtil.NOTIFICATION_RECORD_SYNC);
} finally {
database.close();
}
sendBroadcastNotification();
}
/**
* Sync a Record batch, and return the Set of Record IDs within the response
*/
@NonNull
private Pair<Set<String>, Boolean> sync(@NonNull SurveyDbAdapter database, @NonNull FlowApi api,
long surveyGroupId)
throws IOException {
final String syncTime = database.getSyncTime(surveyGroupId);
Set<String> records = new HashSet<>();
Timber.d("sync() - SurveyGroup: " + surveyGroupId + ". SyncTime: " + syncTime);
List<SurveyedLocale> locales = api
.getSurveyedLocales(surveyGroupId, syncTime);
boolean correctData = true;
if (locales != null) {
for (SurveyedLocale locale : locales) {
List<SurveyInstance> surveyInstances = locale.getSurveyInstances();
if (surveyInstances == null || surveyInstances.isEmpty()) {
correctData = false;
}
database.syncSurveyedLocale(locale);
records.add(locale.getId());
}
}
//Delete empty or corrupted data received from server
database.deleteEmptyRecords();
return new Pair<>(records, correctData);
}
private void displayToast(final String text) {
mHandler.post(new ServiceToastRunnable(getApplicationContext(), text));
}
/**
* Dispatch a Broadcast notification to notify of SurveyedLocales synchronization.
* This notification will be received in {@link org.akvo.flow.ui.fragment.DatapointsFragment}, in order to
* refresh its data
*/
private void sendBroadcastNotification() {
Intent intentBroadcast = new Intent(ConstantUtil.ACTION_LOCALE_SYNC);
LocalBroadcastManager.getInstance(this).sendBroadcast(intentBroadcast);
}
}