package com.boardgamegeek.service;
import android.accounts.Account;
import android.content.Context;
import android.content.SyncResult;
import android.support.annotation.NonNull;
import android.support.v4.util.ArrayMap;
import android.text.TextUtils;
import com.boardgamegeek.R;
import com.boardgamegeek.auth.Authenticator;
import com.boardgamegeek.io.BggService;
import com.boardgamegeek.io.CollectionRequest;
import com.boardgamegeek.io.CollectionResponse;
import com.boardgamegeek.model.persister.CollectionPersister;
import com.boardgamegeek.provider.BggContract.Collection;
import com.boardgamegeek.util.PreferencesUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import hugo.weaving.DebugLog;
import timber.log.Timber;
/**
* Syncs the user's complete collection in brief mode, one collection status at a time, deleting all items from the local
* database that weren't synced.
*/
public class SyncCollectionComplete extends SyncTask {
private List<String> statuses;
private String[] statusEntries;
private String[] statusValues;
@DebugLog
public SyncCollectionComplete(Context context, BggService service) {
super(context, service);
}
@DebugLog
@Override
public int getSyncType() {
return SyncService.FLAG_SYNC_COLLECTION_DOWNLOAD;
}
@DebugLog
@Override
public void execute(@NonNull Account account, @NonNull SyncResult syncResult) {
Timber.i("Syncing full collection list...");
try {
CollectionPersister persister = new CollectionPersister.Builder(context)
.includePrivateInfo()
.includeStats()
.build();
statusEntries = context.getResources().getStringArray(R.array.pref_sync_status_entries);
statusValues = context.getResources().getStringArray(R.array.pref_sync_status_values);
statuses = getSyncableStatuses();
for (int i = 0; i < statuses.size(); i++) {
if (isCancelled()) {
Timber.i("...cancelled");
return;
}
String status = statuses.get(i);
if (TextUtils.isEmpty(status)) {
Timber.i("...skipping blank status");
continue;
}
Timber.i("...syncing status [%s]", status);
String statusDescription = getStatusDescription(status);
updateProgressNotification(context.getString(R.string.sync_notification_collection_items, statusDescription));
if (i > 0) if (wasSleepInterrupted(5000)) return;
ArrayMap<String, String> options = createOptions(i, status);
CollectionResponse response = new CollectionRequest(service, account.name, options).execute();
if (response.hasError()) {
showError(response.getError());
syncResult.stats.numIoExceptions++;
return;
} else if (response.getNumberOfItems() > 0) {
int rows = persister.save(response.getItems()).getRecordCount();
syncResult.stats.numEntries += response.getNumberOfItems();
Timber.i("...saved %,d records for %,d collection items", rows, response.getNumberOfItems());
} else {
Timber.i("...no collection items to save");
}
if (isCancelled()) {
Timber.i("...cancelled");
return;
}
if (wasSleepInterrupted(2000)) return;
updateProgressNotification(context.getString(R.string.sync_notification_collection_accessories, statusDescription));
options.put(BggService.COLLECTION_QUERY_KEY_SUBTYPE, BggService.THING_SUBTYPE_BOARDGAME_ACCESSORY);
response = new CollectionRequest(service, account.name, options).execute();
if (response.hasError()) {
showError(response.getError());
syncResult.stats.numIoExceptions++;
return;
} else if (response.getNumberOfItems() > 0) {
int rows = persister.save(response.getItems()).getRecordCount();
syncResult.stats.numEntries += response.getNumberOfItems();
Timber.i("...saved %,d records for %,d collection accessories", rows, response.getNumberOfItems());
} else {
Timber.i("...no collection accessories to save");
}
}
final long initialTimestamp = persister.getInitialTimestamp();
deleteUnusedItems(initialTimestamp);
updateTimestamps(initialTimestamp);
} finally {
Timber.i("...complete!");
}
}
@DebugLog
@NonNull
private List<String> getSyncableStatuses() {
List<String> statuses = new ArrayList<>(Arrays.asList(PreferencesUtils.getSyncStatuses(context)));
// Played games should be synced first - they don't respect the "exclude" flag
if (statuses.remove(BggService.COLLECTION_QUERY_STATUS_PLAYED)) {
statuses.add(0, BggService.COLLECTION_QUERY_STATUS_PLAYED);
}
return statuses;
}
@DebugLog
private String getStatusDescription(String status) {
for (int i = 0; i < statusEntries.length; i++) {
if (statusValues[i].equalsIgnoreCase(status)) {
return statusEntries[i];
}
}
return status;
}
@DebugLog
@NonNull
private ArrayMap<String, String> createOptions(int i, String status) {
ArrayMap<String, String> options = new ArrayMap<>();
options.put(BggService.COLLECTION_QUERY_KEY_STATS, "1");
options.put(BggService.COLLECTION_QUERY_KEY_SHOW_PRIVATE, "1");
options.put(status, "1");
for (int j = 0; j < i; j++) {
options.put(statuses.get(j), "0");
}
return options;
}
@DebugLog
private void deleteUnusedItems(long initialTimestamp) {
Timber.i("...deleting old collection entries");
int count = context.getContentResolver().delete(
Collection.CONTENT_URI,
Collection.UPDATED_LIST + "<?",
new String[] { String.valueOf(initialTimestamp) });
Timber.i("...deleted %,d old collection entries", count);
// TODO: delete thumbnail images associated with this list (both collection and game)
}
@DebugLog
private void updateTimestamps(long initialTimestamp) {
Authenticator.putLong(context, SyncService.TIMESTAMP_COLLECTION_COMPLETE, initialTimestamp);
Authenticator.putLong(context, SyncService.TIMESTAMP_COLLECTION_PARTIAL, initialTimestamp);
}
@Override
public int getNotificationSummaryMessageId() {
return R.string.sync_notification_collection_full;
}
}