package com.boardgamegeek.service;
import android.accounts.Account;
import android.content.Context;
import android.content.SyncResult;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import com.boardgamegeek.R;
import com.boardgamegeek.auth.Authenticator;
import com.boardgamegeek.io.BggService;
import com.boardgamegeek.model.PlaysResponse;
import com.boardgamegeek.model.persister.PlayPersister;
import com.boardgamegeek.provider.BggContract.Plays;
import com.boardgamegeek.util.DateTimeUtils;
import com.boardgamegeek.util.PreferencesUtils;
import com.boardgamegeek.util.SelectionBuilder;
import java.io.IOException;
import retrofit2.Call;
import retrofit2.Response;
import timber.log.Timber;
public class SyncPlays extends SyncTask {
private SyncResult syncResult;
private long startTime;
private PlayPersister persister;
public SyncPlays(Context context, BggService service) {
super(context, service);
}
@Override
public int getSyncType() {
return SyncService.FLAG_SYNC_PLAYS_DOWNLOAD;
}
@Override
public void execute(@NonNull Account account, @NonNull SyncResult syncResult) throws IOException {
Timber.i("Syncing plays...");
try {
if (!PreferencesUtils.getSyncPlays(context)) {
Timber.i("...plays not set to sync");
return;
}
this.syncResult = syncResult;
startTime = System.currentTimeMillis();
persister = new PlayPersister(context);
long newestSyncDate = Authenticator.getLong(context, SyncService.TIMESTAMP_PLAYS_NEWEST_DATE, 0);
if (newestSyncDate <= 0) {
if (executeCall(account.name, null, null)) return;
} else {
String date = DateTimeUtils.formatDateForApi(newestSyncDate);
if (executeCall(account.name, date, null)) return;
deleteUnupdatedPlaysSince(newestSyncDate);
}
long oldestDate = Authenticator.getLong(context, SyncService.TIMESTAMP_PLAYS_OLDEST_DATE, Long.MAX_VALUE);
if (oldestDate > 0) {
String date = DateTimeUtils.formatDateForApi(oldestDate);
if (executeCall(account.name, null, date)) return;
deleteUnupdatedPlaysBefore(oldestDate);
Authenticator.putLong(context, SyncService.TIMESTAMP_PLAYS_OLDEST_DATE, 0);
}
SyncService.calculateAndUpdateHIndex(context);
} finally {
Timber.i("...complete!");
}
}
private boolean executeCall(String username, String minDate, String maxDate) {
Response<PlaysResponse> response;
int page = 1;
do {
if (isCancelled()) {
Timber.i("...cancelled early");
return true;
}
if (page != 1) if (wasSleepInterrupted(3000)) return true;
showNotification(minDate, maxDate, page);
Call<PlaysResponse> call = service.plays(username, minDate, maxDate, page);
try {
response = call.execute();
if (!response.isSuccessful()) {
showError(String.format("Unsuccessful plays fetch with code: %s", response.code()));
syncResult.stats.numIoExceptions++;
return true;
}
} catch (IOException e) {
showError(String.format("Unsuccessful plays fetch with exception: %s", e.getLocalizedMessage()));
syncResult.stats.numIoExceptions++;
return true;
}
persist(response.body());
updateTimestamps(response.body());
page++;
} while (response.body().hasMorePages());
return false;
}
private void showNotification(String minDate, String maxDate, int page) {
String message;
if (TextUtils.isEmpty(minDate) && TextUtils.isEmpty(maxDate)) {
message = context.getString(R.string.sync_notification_plays_all);
} else if (TextUtils.isEmpty(minDate)) {
message = context.getString(R.string.sync_notification_plays_old, maxDate);
} else if (TextUtils.isEmpty(maxDate)) {
message = context.getString(R.string.sync_notification_plays_new, minDate);
} else {
message = context.getString(R.string.sync_notification_plays_between, minDate, maxDate);
}
if (page > 1) {
message = context.getString(R.string.sync_notification_page_suffix, message, page);
}
updateProgressNotification(message);
}
private void persist(@NonNull PlaysResponse response) {
if (response.plays != null && response.plays.size() > 0) {
if (persister == null) {
persister = new PlayPersister(context);
}
persister.save(response.plays, startTime);
syncResult.stats.numEntries += response.plays.size();
Timber.i("...saved " + response.plays.size() + " plays");
} else {
Timber.i("...no plays to update");
}
}
private void deleteUnupdatedPlaysSince(long time) {
deleteUnupdatedPlays(time, ">=");
}
private void deleteUnupdatedPlaysBefore(long time) {
deleteUnupdatedPlays(time, "<=");
}
private void deleteUnupdatedPlays(long time, final String dateComparator) {
deletePlays(Plays.SYNC_TIMESTAMP + "<? AND " + Plays.DATE + dateComparator + "? AND " +
SelectionBuilder.whereZeroOrNull(Plays.UPDATE_TIMESTAMP) + " AND " +
SelectionBuilder.whereZeroOrNull(Plays.DELETE_TIMESTAMP) + " AND " +
SelectionBuilder.whereZeroOrNull(Plays.DIRTY_TIMESTAMP),
new String[] { String.valueOf(startTime), DateTimeUtils.formatDateForApi(time) });
}
private void deletePlays(String selection, String[] selectionArgs) {
int count = context.getContentResolver().delete(Plays.CONTENT_URI, selection, selectionArgs);
syncResult.stats.numDeletes += count;
Timber.i("...deleted %,d unupdated plays", count);
}
private void updateTimestamps(@NonNull PlaysResponse response) {
long newestDate = Authenticator.getLong(context, SyncService.TIMESTAMP_PLAYS_NEWEST_DATE, 0);
if (response.getNewestDate() > newestDate) {
Authenticator.putLong(context, SyncService.TIMESTAMP_PLAYS_NEWEST_DATE, response.getNewestDate());
}
long oldestDate = Authenticator.getLong(context, SyncService.TIMESTAMP_PLAYS_OLDEST_DATE, Long.MAX_VALUE);
if (response.getOldestDate() < oldestDate) {
Authenticator.putLong(context, SyncService.TIMESTAMP_PLAYS_OLDEST_DATE, response.getOldestDate());
}
}
@Override
public int getNotificationSummaryMessageId() {
return R.string.sync_notification_plays;
}
}