cz.maresmar.sfm.utils.ActionUtils.java Source code

Java tutorial

Introduction

Here is the source code for cz.maresmar.sfm.utils.ActionUtils.java

Source

/*
 * SmartFoodMenu - Android application for canteens extendable with plugins
 *
 * Copyright  2016-2018  Martin Mare <mmrmartin[at]gmail[dot]com>
 *
 * This program 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.
 *
 * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package cz.maresmar.sfm.utils;

import android.app.PendingIntent;
import android.content.ContentProviderOperation;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import android.support.annotation.WorkerThread;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.widget.Toast;

import java.util.ArrayList;

import cz.maresmar.sfm.Assert;
import cz.maresmar.sfm.BuildConfig;
import cz.maresmar.sfm.R;
import cz.maresmar.sfm.app.NotificationContract;
import cz.maresmar.sfm.db.DbContract;
import cz.maresmar.sfm.provider.ProviderContract;
import cz.maresmar.sfm.provider.PublicProviderContract;
import cz.maresmar.sfm.service.plugin.sync.SyncHandler;
import cz.maresmar.sfm.view.MainActivity;
import timber.log.Timber;

/**
 * Utils for Actions. Helps check actions for sync results and provides methods to make changes in them.
 */
public class ActionUtils {

    public static final String BROADCAST_ACTION_EVENT = "cz.maresmar.sfm.broadcast.action-event";

    public static final String ARG_EVENT = "event";
    public static final String EVENT_SYNC_FAILED = "actionSyncFailed";

    // To prevent someone from accidentally instantiating the utils class,
    // make the constructor private.
    private ActionUtils() {
    }

    /**
     * Make edits in Actions for corresponding Menu entry. These action are saved as
     * {@link ProviderContract#ACTION_SYNC_STATUS_LOCAL} then.
     * <p>
     * Handles menu group restrictions (like one order per group), in such cases creates
     * {@link ProviderContract#ACTION_ENTRY_TYPE_VIRTUAL} actions to override the
     * {@link ProviderContract#ACTION_SYNC_STATUS_SYNCED} ones. If an action reserves nothing,
     * the action is removed.
     * </p>
     *
     * @param context    Some valid context
     * @param userUri    User Uri prefix
     * @param relativeId Relative ID of corresponding Menu entry
     * @param portalId   Portal ID of corresponding Menu entry
     * @param reserved   New amount of reserved food
     * @param offered    New amount of offered food
     */
    @WorkerThread
    public static void makeEdit(@NonNull Context context, @NonNull Uri userUri, long relativeId, long portalId,
            int reserved, int offered) {
        // Load the corresponding menu entry
        @ProviderContract.PortalFeatures
        int portalFeatures;
        long menuGroupId;
        int price;
        long date;
        int syncedReserved, syncedOffered, syncedTaken;
        boolean hasLocal;
        int localReserved, localOffered;
        long portalGroupId;

        Uri menuUri = Uri.withAppendedPath(userUri, ProviderContract.MENU_ENTRY_PATH);
        ArrayList<ContentProviderOperation> ops = new ArrayList<>();

        try (Cursor menuCursor = context.getContentResolver().query(menuUri, new String[] {
                ProviderContract.MenuEntry.PORTAL_FEATURES, ProviderContract.MenuEntry.GROUP_ID,
                ProviderContract.MenuEntry.PRICE, ProviderContract.MenuEntry.DATE,
                ProviderContract.MenuEntry.SYNCED_RESERVED_AMOUNT, ProviderContract.MenuEntry.SYNCED_OFFERED_AMOUNT,
                ProviderContract.MenuEntry.SYNCED_TAKEN_AMOUNT, ProviderContract.MenuEntry.LOCAL_RESERVED_AMOUNT,
                ProviderContract.MenuEntry.LOCAL_OFFERED_AMOUNT, ProviderContract.MenuEntry.PORTAL_GROUP_ID },
                ProviderContract.MenuEntry.ME_RELATIVE_ID + " = " + relativeId + " AND "
                        + ProviderContract.MenuEntry.PORTAL_ID + " = " + portalId,
                null, null)) {

            if (BuildConfig.DEBUG) {
                Assert.isOne(menuCursor.getCount());
            }

            menuCursor.moveToFirst();

            // Info
            portalFeatures = menuCursor.getInt(0);
            menuGroupId = menuCursor.getLong(1);
            price = menuCursor.getInt(2);
            date = menuCursor.getLong(3);

            // Synced
            syncedReserved = menuCursor.getInt(4);
            syncedOffered = menuCursor.getInt(5);
            syncedTaken = menuCursor.getInt(6);

            // Local
            hasLocal = !menuCursor.isNull(7);
            localReserved = menuCursor.getInt(7);
            localOffered = menuCursor.getInt(8);

            // Portal group
            portalGroupId = menuCursor.getLong(9);

            // Insert changes
            Uri actionUri = Uri.withAppendedPath(userUri, ProviderContract.ACTION_PATH);
            // Insert virtual group changes
            boolean restrictToOneOrderPerGroup = (portalFeatures
                    & ProviderContract.FEATURE_RESTRICT_TO_ONE_ORDER_PER_GROUP) == ProviderContract.FEATURE_RESTRICT_TO_ONE_ORDER_PER_GROUP;

            // Delete old edits as I want something new
            if (!restrictToOneOrderPerGroup) {
                // Delete action for this menu entry
                ops.add((ContentProviderOperation.newDelete(actionUri)
                        .withSelection(ProviderContract.Action.ME_RELATIVE_ID + " = " + relativeId + " AND "
                                + ProviderContract.Action.ME_PORTAL_ID + " = " + portalId + " AND "
                                + ProviderContract.Action.SYNC_STATUS + " = "
                                + ProviderContract.ACTION_SYNC_STATUS_EDIT, null)
                        .build()));
            } else {
                // Delete actions for whole menu entry group
                ops.add(ContentProviderOperation.newDelete(actionUri)
                        .withSelection(ProviderContract.Action.ME_PORTAL_ID + " IN " + "(SELECT "
                                + DbContract.Portal._ID + " FROM " + DbContract.Portal.TABLE_NAME + " WHERE "
                                + DbContract.Portal.COLUMN_NAME_PGID + " == " + portalGroupId + " ) AND "
                                + ProviderContract.Action.SYNC_STATUS + " = "
                                + ProviderContract.ACTION_SYNC_STATUS_EDIT + " AND " + "EXISTS ( SELECT * FROM "
                                + DbContract.MenuEntry.TABLE_NAME + " WHERE " + DbContract.MenuEntry.COLUMN_NAME_PID
                                + " == " + ProviderContract.Action.ME_PORTAL_ID + " AND "
                                + DbContract.MenuEntry.COLUMN_NAME_RELATIVE_ID + " == "
                                + ProviderContract.Action.ME_RELATIVE_ID + " AND "
                                + DbContract.MenuEntry.COLUMN_NAME_DATE + " == " + date + " AND "
                                + DbContract.MenuEntry.COLUMN_NAME_MGID + " == " + menuGroupId + " )", null)
                        .build());
            }

            // Insert new edits
            if ((hasLocal && !(reserved == localReserved && offered == localOffered))
                    || (!hasLocal && !(reserved == syncedReserved && offered == syncedOffered))) {
                if (restrictToOneOrderPerGroup) {
                    // Sets other actions in group to zeros
                    try (Cursor groupCursor = context.getContentResolver().query(menuUri, new String[] {
                            ProviderContract.MenuEntry.ME_RELATIVE_ID, ProviderContract.MenuEntry.PRICE,
                            ProviderContract.MenuEntry.STATUS, ProviderContract.MenuEntry.SYNCED_RESERVED_AMOUNT,
                            ProviderContract.MenuEntry.SYNCED_TAKEN_AMOUNT, ProviderContract.MenuEntry.PORTAL_ID },
                            ProviderContract.MenuEntry.PORTAL_ID + " IN " + "(SELECT " + DbContract.Portal._ID
                                    + " FROM " + DbContract.Portal.TABLE_NAME + " WHERE "
                                    + DbContract.Portal.COLUMN_NAME_PGID + " == " + portalGroupId + " ) AND "
                                    + ProviderContract.MenuEntry.DATE + " = " + date + " AND "
                                    + ProviderContract.MenuEntry.GROUP_ID + " = " + menuGroupId + " AND ("
                                    + "(IFNULL(" + ProviderContract.MenuEntry.SYNCED_RESERVED_AMOUNT + ", 0)"
                                    + " - IFNULL(" + ProviderContract.MenuEntry.SYNCED_TAKEN_AMOUNT
                                    + ", 0)) > 0 OR " + "(IFNULL("
                                    + ProviderContract.MenuEntry.LOCAL_RESERVED_AMOUNT + ", 0)" + " - IFNULL("
                                    + ProviderContract.MenuEntry.SYNCED_TAKEN_AMOUNT + ", 0)) > 0)",
                            null, null)) {
                        if (groupCursor != null) {
                            while (groupCursor.moveToNext()) {
                                // Skip main changed row
                                if (groupCursor.getLong(0) == relativeId) {
                                    continue;
                                }

                                @ProviderContract.MenuStatus
                                int status = groupCursor.getInt(2);
                                boolean canCancel = (status
                                        & ProviderContract.MENU_STATUS_CANCELABLE) == ProviderContract.MENU_STATUS_CANCELABLE;
                                boolean canUseStock = (status
                                        & ProviderContract.FEATURE_FOOD_STOCK) == ProviderContract.FEATURE_FOOD_STOCK;

                                // Insert virtual actions
                                ContentValues newAction = new ContentValues();
                                newAction.put(ProviderContract.Action.ME_RELATIVE_ID, groupCursor.getLong(0));
                                newAction.put(ProviderContract.Action.ME_PORTAL_ID, groupCursor.getLong(5));
                                newAction.put(ProviderContract.Action.SYNC_STATUS,
                                        ProviderContract.ACTION_SYNC_STATUS_EDIT);
                                newAction.put(ProviderContract.Action.ENTRY_TYPE,
                                        ProviderContract.ACTION_ENTRY_TYPE_VIRTUAL);
                                newAction.put(ProviderContract.Action.PRICE, groupCursor.getInt(1));
                                if (canCancel) {
                                    newAction.put(ProviderContract.Action.RESERVED_AMOUNT, 0);
                                    newAction.put(ProviderContract.Action.OFFERED_AMOUNT, 0);
                                } else {
                                    newAction.put(ProviderContract.Action.RESERVED_AMOUNT, groupCursor.getInt(3));
                                    newAction.put(ProviderContract.Action.OFFERED_AMOUNT, groupCursor.getInt(3));

                                    Toast.makeText(context, R.string.actions_food_stock_on_restricted_to_one,
                                            Toast.LENGTH_LONG).show();
                                }
                                newAction.put(ProviderContract.Action.TAKEN_AMOUNT, groupCursor.getInt(4));

                                ops.add(ContentProviderOperation.newInsert(actionUri).withValues(newAction)
                                        .build());
                            }
                        }
                    }
                }

                // Insert main edit
                ContentValues newAction = new ContentValues();
                newAction.put(ProviderContract.Action.ME_RELATIVE_ID, relativeId);
                newAction.put(ProviderContract.Action.ME_PORTAL_ID, portalId);
                newAction.put(ProviderContract.Action.SYNC_STATUS, ProviderContract.ACTION_SYNC_STATUS_EDIT);
                newAction.put(ProviderContract.Action.ENTRY_TYPE, ProviderContract.ACTION_ENTRY_TYPE_STANDARD);
                newAction.put(ProviderContract.Action.PRICE, price);
                newAction.put(ProviderContract.Action.RESERVED_AMOUNT, reserved);
                newAction.put(ProviderContract.Action.OFFERED_AMOUNT, offered);
                newAction.put(ProviderContract.Action.TAKEN_AMOUNT, syncedTaken);

                ops.add(ContentProviderOperation.newInsert(actionUri).withValues(newAction).build());
            }

            // Apply changes at once (it boost the performance)
            try {
                context.getContentResolver().applyBatch(ProviderContract.AUTHORITY, ops);
            } catch (RemoteException e) {
                e.printStackTrace();
            } catch (OperationApplicationException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Changes {@link ProviderContract#ACTION_SYNC_STATUS_EDIT} actions to {@link ProviderContract#ACTION_SYNC_STATUS_LOCAL}
     * and starts changes sync.
     *
     * @param context Some valid context
     * @param userUri User Uri prefix
     */
    @WorkerThread
    public static void saveEdits(@NonNull Context context, @NonNull Uri userUri) {
        Uri actionUri = Uri.withAppendedPath(userUri, ProviderContract.ACTION_PATH);
        long userId = ContentUris.parseId(userUri);

        // Delete conflict local rows
        int conflictRows = context.getContentResolver().delete(actionUri,
                ProviderContract.Action.SYNC_STATUS + " == " + ProviderContract.ACTION_SYNC_STATUS_LOCAL + " AND "
                        + "EXISTS ( SELECT * FROM " + DbContract.FoodAction.TABLE_NAME + " AS EditAct WHERE "
                        + "EditAct." + DbContract.FoodAction.COLUMN_NAME_CID + " == "
                        + ProviderContract.Action.CREDENTIAL_ID + " AND " + "EditAct."
                        + DbContract.FoodAction.COLUMN_NAME_ME_PID + " == " + ProviderContract.Action.ME_PORTAL_ID
                        + " AND " + "EditAct." + DbContract.FoodAction.COLUMN_NAME_ME_RELATIVE_ID + " == "
                        + ProviderContract.Action.ME_RELATIVE_ID + " AND " + "EditAct."
                        + DbContract.FoodAction.COLUMN_NAME_SYNC_STATUS + " == "
                        + ProviderContract.ACTION_SYNC_STATUS_EDIT + " )",
                null);
        Timber.d("Deleted %d rows with conflict local values", conflictRows);

        // Save rows that has different values then the synced ones
        ContentValues newValues = new ContentValues();
        newValues.put(ProviderContract.Action.SYNC_STATUS, ProviderContract.ACTION_SYNC_STATUS_LOCAL);
        newValues.put(ProviderContract.Action.LAST_CHANGE, System.currentTimeMillis());

        int updatedRows = context.getContentResolver().update(actionUri, newValues,
                ProviderContract.Action.SYNC_STATUS + " == " + ProviderContract.ACTION_SYNC_STATUS_EDIT + " AND ("
                        + "NOT EXISTS (SELECT * FROM " + DbContract.FoodAction.TABLE_NAME + " AS SyncAct WHERE "
                        + "SyncAct." + DbContract.FoodAction.COLUMN_NAME_CID + " IN (SELECT "
                        + DbContract.Credential._ID + " FROM " + DbContract.Credential.TABLE_NAME + " WHERE "
                        + DbContract.Credential.COLUMN_NAME_UID + " == " + userId + ") AND " + "SyncAct."
                        + DbContract.FoodAction.COLUMN_NAME_ME_PID + " == " + ProviderContract.Action.ME_PORTAL_ID
                        + " AND " + "SyncAct." + DbContract.FoodAction.COLUMN_NAME_ME_RELATIVE_ID + " == "
                        + ProviderContract.Action.ME_RELATIVE_ID + " AND " + "SyncAct."
                        + DbContract.FoodAction.COLUMN_NAME_SYNC_STATUS + " == "
                        + ProviderContract.ACTION_SYNC_STATUS_SYNCED + " ) " + "AND ("
                        + ProviderContract.Action.RESERVED_AMOUNT + " > 0 OR "
                        + ProviderContract.Action.OFFERED_AMOUNT + " > 0 ) " + "OR EXISTS (SELECT * FROM "
                        + DbContract.FoodAction.TABLE_NAME + " AS SyncAct WHERE " + "SyncAct."
                        + DbContract.FoodAction.COLUMN_NAME_CID + " IN (SELECT " + DbContract.Credential._ID
                        + " FROM " + DbContract.Credential.TABLE_NAME + " WHERE "
                        + DbContract.Credential.COLUMN_NAME_UID + " == " + userId + ") AND " + "SyncAct."
                        + DbContract.FoodAction.COLUMN_NAME_ME_PID + " == " + ProviderContract.Action.ME_PORTAL_ID
                        + " AND " + "SyncAct." + DbContract.FoodAction.COLUMN_NAME_ME_RELATIVE_ID + " == "
                        + ProviderContract.Action.ME_RELATIVE_ID + " AND " + "SyncAct."
                        + DbContract.FoodAction.COLUMN_NAME_SYNC_STATUS + " == "
                        + ProviderContract.ACTION_SYNC_STATUS_SYNCED + " AND (" + "SyncAct."
                        + DbContract.FoodAction.COLUMN_NAME_RESERVED_AMOUNT + " != "
                        + ProviderContract.Action.RESERVED_AMOUNT + " OR " + "SyncAct."
                        + DbContract.FoodAction.COLUMN_NAME_OFFERED_AMOUNT + " != "
                        + ProviderContract.Action.OFFERED_AMOUNT + " ) ) )",
                null);

        // Delete rest
        int unnecessaryRows = context.getContentResolver().delete(actionUri,
                ProviderContract.Action.SYNC_STATUS + " == " + ProviderContract.ACTION_SYNC_STATUS_EDIT, null);

        // Check invariants
        if (BuildConfig.DEBUG) {
            Assert.that(updatedRows + unnecessaryRows > 0, "Should change at least one rows");
            Assert.that(conflictRows <= updatedRows + unnecessaryRows, "Conflict rows overflow");
        }

        SyncHandler.planChangesSync(context);
    }

    /**
     * Deletes all {@link ProviderContract#ACTION_SYNC_STATUS_EDIT} actions
     *
     * @param context Some valid context
     * @param userUri User Uri prefix
     */
    @WorkerThread
    public static void discardEdits(@NonNull Context context, @NonNull Uri userUri) {
        Uri actionUri = Uri.withAppendedPath(userUri, ProviderContract.ACTION_PATH);
        int affectedRows = context.getContentResolver().delete(actionUri,
                ProviderContract.Action.SYNC_STATUS + " == " + ProviderContract.ACTION_SYNC_STATUS_EDIT, null);

        if (BuildConfig.DEBUG && affectedRows == 0) {
            Timber.w("Should delete at least one edit");
        }
    }

    /**
     * Check if all {@link ProviderContract#ACTION_SYNC_STATUS_LOCAL} actions were synced. If some
     * action fails the notification is shown.
     *
     * @param context Some valid context
     */
    @WorkerThread
    public static void checkActionResults(@NonNull Context context) {

        ContentValues newValues = new ContentValues();
        newValues.put(ProviderContract.Action.SYNC_STATUS, ProviderContract.ACTION_SYNC_STATUS_FAILED);
        newValues.put(ProviderContract.Action.LAST_CHANGE, System.currentTimeMillis());

        // Mark not synced actions as failed
        int failedActions = context.getContentResolver().update(ProviderContract.Action.getUri(), newValues,
                ProviderContract.Action.SYNC_STATUS + " == " + ProviderContract.ACTION_SYNC_STATUS_LOCAL + " AND "
                        + "(" + "(EXISTS ( SELECT * FROM " + DbContract.FoodAction.TABLE_NAME
                        + " AS SyncedAct WHERE " + "SyncedAct." + DbContract.FoodAction.COLUMN_NAME_CID + " == "
                        + ProviderContract.Action.CREDENTIAL_ID + " AND " + "SyncedAct."
                        + DbContract.FoodAction.COLUMN_NAME_ME_PID + " == " + ProviderContract.Action.ME_PORTAL_ID
                        + " AND " + "SyncedAct." + DbContract.FoodAction.COLUMN_NAME_ME_RELATIVE_ID + " == "
                        + ProviderContract.Action.ME_RELATIVE_ID + " AND " + "SyncedAct."
                        + DbContract.FoodAction.COLUMN_NAME_SYNC_STATUS + " == "
                        + ProviderContract.ACTION_SYNC_STATUS_SYNCED + " AND " + "(SyncedAct."
                        + DbContract.FoodAction.COLUMN_NAME_RESERVED_AMOUNT + " != "
                        + ProviderContract.Action.RESERVED_AMOUNT + " OR " + "SyncedAct."
                        + DbContract.FoodAction.COLUMN_NAME_OFFERED_AMOUNT + " != "
                        + ProviderContract.Action.OFFERED_AMOUNT + "))" + ") OR (" + "NOT EXISTS ( SELECT * FROM "
                        + DbContract.FoodAction.TABLE_NAME + " AS SyncedAct WHERE " + "SyncedAct."
                        + DbContract.FoodAction.COLUMN_NAME_CID + " == " + ProviderContract.Action.CREDENTIAL_ID
                        + " AND " + "SyncedAct." + DbContract.FoodAction.COLUMN_NAME_ME_PID + " == "
                        + ProviderContract.Action.ME_PORTAL_ID + " AND " + "SyncedAct."
                        + DbContract.FoodAction.COLUMN_NAME_ME_RELATIVE_ID + " == "
                        + ProviderContract.Action.ME_RELATIVE_ID + " AND " + "SyncedAct."
                        + DbContract.FoodAction.COLUMN_NAME_SYNC_STATUS + " == "
                        + ProviderContract.ACTION_SYNC_STATUS_SYNCED + " AND " + "SyncedAct."
                        + DbContract.FoodAction.COLUMN_NAME_RESERVED_AMOUNT + " == "
                        + ProviderContract.Action.RESERVED_AMOUNT + " AND " + "SyncedAct."
                        + DbContract.FoodAction.COLUMN_NAME_OFFERED_AMOUNT + " == "
                        + ProviderContract.Action.OFFERED_AMOUNT + ") AND " + "("
                        + ProviderContract.Action.RESERVED_AMOUNT + " != 0 OR "
                        + ProviderContract.Action.OFFERED_AMOUNT + " != 0))" + ")",
                null);

        if (failedActions > 0) {
            Timber.e("%d action failed to sync", failedActions);

            // Create an explicit intent for an Activity in your app
            PendingIntent pendingIntent = MainActivity.getShowOrdersIntent(context);

            NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context,
                    NotificationContract.FAILED_ACTIONS_CHANNEL_ID).setSmallIcon(R.drawable.notification_icon)
                            .setColor(ContextCompat.getColor(context, R.color.colorPrimary))
                            .setContentTitle(context.getString(R.string.notification_order_failed_title))
                            .setContentText(
                                    context.getString(R.string.notification_order_failed_text, failedActions))
                            .setPriority(NotificationCompat.PRIORITY_MAX)
                            // Set the intent that will fire when the user taps the notification
                            .setContentIntent(pendingIntent).setAutoCancel(true);

            NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
            // notificationId is a unique int for each notification that you must define
            notificationManager.notify(NotificationContract.FAILED_ACTION_ID, mBuilder.build());

            Intent intent = new Intent(BROADCAST_ACTION_EVENT);
            intent.putExtra(ARG_EVENT, EVENT_SYNC_FAILED);

            LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
            localBroadcastManager.sendBroadcast(intent);
        }

        // Delete fully synced actions
        int doneOrders = context.getContentResolver().delete(ProviderContract.Action.getUri(),
                ProviderContract.Action.SYNC_STATUS + " == " + ProviderContract.ACTION_SYNC_STATUS_LOCAL + " AND "
                        + "(" + "(EXISTS ( SELECT * FROM " + DbContract.FoodAction.TABLE_NAME
                        + " AS SyncedAct WHERE " + "SyncedAct." + DbContract.FoodAction.COLUMN_NAME_CID + " == "
                        + ProviderContract.Action.CREDENTIAL_ID + " AND " + "SyncedAct."
                        + DbContract.FoodAction.COLUMN_NAME_ME_PID + " == " + ProviderContract.Action.ME_PORTAL_ID
                        + " AND " + "SyncedAct." + DbContract.FoodAction.COLUMN_NAME_ME_RELATIVE_ID + " == "
                        + ProviderContract.Action.ME_RELATIVE_ID + " AND " + "SyncedAct."
                        + DbContract.FoodAction.COLUMN_NAME_SYNC_STATUS + " == "
                        + ProviderContract.ACTION_SYNC_STATUS_SYNCED + " AND " + "SyncedAct."
                        + DbContract.FoodAction.COLUMN_NAME_RESERVED_AMOUNT + " == "
                        + ProviderContract.Action.RESERVED_AMOUNT + " AND " + "SyncedAct."
                        + DbContract.FoodAction.COLUMN_NAME_OFFERED_AMOUNT + " == "
                        + ProviderContract.Action.OFFERED_AMOUNT + ") " + ") OR (" + "NOT EXISTS ( SELECT * FROM "
                        + DbContract.FoodAction.TABLE_NAME + " AS SyncedAct WHERE " + "SyncedAct."
                        + DbContract.FoodAction.COLUMN_NAME_CID + " == " + ProviderContract.Action.CREDENTIAL_ID
                        + " AND " + "SyncedAct." + DbContract.FoodAction.COLUMN_NAME_ME_PID + " == "
                        + ProviderContract.Action.ME_PORTAL_ID + " AND " + "SyncedAct."
                        + DbContract.FoodAction.COLUMN_NAME_ME_RELATIVE_ID + " == "
                        + ProviderContract.Action.ME_RELATIVE_ID + " AND " + "SyncedAct."
                        + DbContract.FoodAction.COLUMN_NAME_SYNC_STATUS + " == "
                        + ProviderContract.ACTION_SYNC_STATUS_SYNCED + " AND " + "(SyncedAct."
                        + DbContract.FoodAction.COLUMN_NAME_RESERVED_AMOUNT + " != "
                        + ProviderContract.Action.RESERVED_AMOUNT + " OR " + "SyncedAct."
                        + DbContract.FoodAction.COLUMN_NAME_OFFERED_AMOUNT + " != "
                        + ProviderContract.Action.OFFERED_AMOUNT + ")) AND "
                        + ProviderContract.Action.RESERVED_AMOUNT + " == 0 AND "
                        + ProviderContract.Action.OFFERED_AMOUNT + " == 0)" + ")",
                null);

        Timber.i("%d actions synced", doneOrders);
    }

    /**
     * Deletes old failed actions that are in conflict with local actions that will be synced
     *
     * @param context Some valid context
     */
    @WorkerThread
    public static void deleteConflictFailedActions(@NonNull Context context) {
        int affectedRows = context.getContentResolver().delete(ProviderContract.Action.getUri(),
                ProviderContract.Action.SYNC_STATUS + " == " + ProviderContract.ACTION_SYNC_STATUS_FAILED + " AND "
                        + "EXISTS ( SELECT * FROM " + DbContract.FoodAction.TABLE_NAME + " AS LocAct WHERE "
                        + "LocAct." + DbContract.FoodAction.COLUMN_NAME_CID + " == "
                        + ProviderContract.Action.CREDENTIAL_ID + " AND " + "LocAct."
                        + DbContract.FoodAction.COLUMN_NAME_ME_PID + " == " + ProviderContract.Action.ME_PORTAL_ID
                        + " AND " + "LocAct." + DbContract.FoodAction.COLUMN_NAME_ME_RELATIVE_ID + " == "
                        + ProviderContract.Action.ME_RELATIVE_ID + " AND " + "LocAct."
                        + DbContract.FoodAction.COLUMN_NAME_SYNC_STATUS + " == "
                        + ProviderContract.ACTION_SYNC_STATUS_LOCAL + ")",
                null);

        Timber.w("%d conflict failed actions deleted", affectedRows);
    }
}