package com.truckmuncher.app.data.sync; import android.content.ContentProviderClient; import android.content.ContentProviderOperation; import android.content.ContentValues; import android.content.OperationApplicationException; import android.content.SyncResult; import android.database.Cursor; import android.os.RemoteException; import com.truckmuncher.api.menu.MenuItemAvailability; import com.truckmuncher.api.menu.MenuService; import com.truckmuncher.api.menu.ModifyMenuItemAvailabilityRequest; import com.truckmuncher.app.data.ApiException; import com.truckmuncher.app.data.Contract; import com.truckmuncher.app.data.PublicContract; import com.truckmuncher.app.data.sql.WhereClause; import java.util.ArrayList; import java.util.List; import static com.truckmuncher.app.data.sql.WhereClause.Operator.EQUALS; public final class MenuItemAvailabilitySyncTask extends SyncTask { private final ContentProviderClient provider; private final MenuService menuService; private final ApiExceptionResolver apiExceptionResolver; public MenuItemAvailabilitySyncTask(ContentProviderClient provider, MenuService menuService, ApiExceptionResolver apiExceptionResolver) { this.provider = provider; this.menuService = menuService; this.apiExceptionResolver = apiExceptionResolver; } @Override public ApiResult sync(SyncResult syncResult) throws RemoteException { WhereClause whereClause = new WhereClause.Builder() .where(Contract.MenuItem.IS_DIRTY, EQUALS, true) .build(); Cursor cursor = provider.query(PublicContract.MENU_ITEM_URI, MenuItemAvailabilityQuery.PROJECTION, whereClause.selection, whereClause.selectionArgs, null); if (!cursor.moveToFirst()) { // Cursor is empty. Probably already synced this. cursor.close(); return ApiResult.OK; } // Construct all the diffs for the request final List<MenuItemAvailability> diff = new ArrayList<>(cursor.getCount()); do { diff.add( new MenuItemAvailability.Builder() .menuItemId(cursor.getString(MenuItemAvailabilityQuery.ID)) .isAvailable(cursor.getInt(MenuItemAvailabilityQuery.IS_AVAILABLE) == 1) .build() ); } while (cursor.moveToNext()); cursor.close(); // Setup and run the request synchronously ModifyMenuItemAvailabilityRequest request = new ModifyMenuItemAvailabilityRequest(diff); try { menuService.modifyMenuItemAvailability(request); // On a successful response, clear the dirty state, but only for values we synced. User might have changed others in the mean time. ArrayList<ContentProviderOperation> operations = new ArrayList<>(diff.size()); for (MenuItemAvailability availability : diff) { ContentValues values = new ContentValues(); values.put(Contract.MenuItem.IS_DIRTY, false); WhereClause where = new WhereClause.Builder() .where(PublicContract.MenuItem.ID, EQUALS, availability.menuItemId) .build(); operations.add( // Since we're clearing an internal state, don't notify listeners ContentProviderOperation.newUpdate(Contract.suppressNotify(PublicContract.MENU_ITEM_URI)) .withSelection(where.selection, where.selectionArgs) .withValues(values) .build() ); } provider.applyBatch(operations); } catch (ApiException e) { return apiExceptionResolver.resolve(e); } catch (OperationApplicationException e) { syncResult.stats.numIoExceptions++; } return ApiResult.OK; } interface MenuItemAvailabilityQuery { static final String[] PROJECTION = new String[]{ PublicContract.MenuItem.ID, PublicContract.MenuItem.IS_AVAILABLE }; static final int ID = 0; static final int IS_AVAILABLE = 1; } }