dev.drsoran.moloko.sync.util.SyncUtils.java Source code

Java tutorial

Introduction

Here is the source code for dev.drsoran.moloko.sync.util.SyncUtils.java

Source

/* 
 *   Copyright (c) 2012 Ronny Rhricht
 *
 *   This file is part of Moloko.
 *
 *   Moloko 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.
 *
 *   Moloko 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 Moloko.  If not, see <http://www.gnu.org/licenses/>.
 *
 *   Contributors:
 * Ronny Rhricht - implementation
 */

package dev.drsoran.moloko.sync.util;

import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import android.accounts.Account;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SyncResult;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.util.Pair;

import com.mdt.rtm.ServiceException;
import com.mdt.rtm.ServiceInternalException;

import dev.drsoran.moloko.MolokoApp;
import dev.drsoran.moloko.connection.ConnectionUtil;
import dev.drsoran.moloko.content.Modification;
import dev.drsoran.moloko.content.RtmProvider;
import dev.drsoran.moloko.content.SyncProviderPart;
import dev.drsoran.moloko.sync.Constants;
import dev.drsoran.moloko.sync.operation.INoopSyncOperation;
import dev.drsoran.moloko.sync.operation.IServerSyncOperation;
import dev.drsoran.moloko.util.AccountUtils;
import dev.drsoran.moloko.util.Intents;
import dev.drsoran.moloko.util.UIUtils;
import dev.drsoran.provider.Rtm;
import dev.drsoran.provider.Rtm.Sync;

public final class SyncUtils {
    private SyncUtils() {
        throw new AssertionError("This class should not be instantiated.");
    }

    public final static void handleServiceInternalException(ServiceInternalException exception, Class<?> tag,
            SyncResult syncResult) {
        final Exception internalException = exception.getEnclosedException();

        if (internalException != null) {
            MolokoApp.Log.e(tag, exception.responseMessage, internalException);

            if (internalException instanceof IOException)
                ++syncResult.stats.numIoExceptions;
            else
                ++syncResult.stats.numParseExceptions;
        } else {
            MolokoApp.Log.e(tag, exception.responseMessage);
            ++syncResult.stats.numIoExceptions;
        }
    }

    public final static void requestManualSync(FragmentActivity activity) {
        requestManualSync(activity, false);
    }

    public final static void requestManualSync(FragmentActivity activity, boolean silent) {
        if (ConnectionUtil.isConnected(activity)) {
            final Account account = AccountUtils.getRtmAccount(activity);

            if (account != null) {
                SyncUtils.requestManualSync(account);
            }

            else if (!silent) {
                UIUtils.showNoAccountDialog(activity);
            }
        } else if (!silent) {
            UIUtils.showNotConnectedDialog(activity);
        }
    }

    public final static void requestManualSync(Account account) {
        final Bundle bundle = new Bundle();

        bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
        bundle.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);

        ContentResolver.requestSync(account, Rtm.AUTHORITY, bundle);
    }

    public final static void requestSettingsOnlySync(Context context, Account account) {
        if (account != null) {
            final Bundle bundle = new Bundle();

            bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
            bundle.putBoolean(dev.drsoran.moloko.sync.Constants.SYNC_EXTRAS_ONLY_SETTINGS, true);
            bundle.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);

            ContentResolver.requestSync(account, Rtm.AUTHORITY, bundle);
        } else {
            // TODO: Show NoAccountDialogFragment if we use PreferenceFragment
            context.startActivity(Intents.createNewAccountIntent());
        }
    }

    public final static void requestScheduledSync(Account account) {
        final Bundle bundle = new Bundle();

        bundle.putBoolean(Constants.SYNC_EXTRAS_SCHEDULED, true);
        bundle.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);

        ContentResolver.requestSync(account, Rtm.AUTHORITY, bundle);
    }

    public final static void cancelSync(Context context) {
        final Account account = AccountUtils.getRtmAccount(context);

        if (account != null)
            ContentResolver.cancelSync(account, Rtm.AUTHORITY);
    }

    public final static boolean isReadyToSync(Context context) {
        // Check if we are connected.
        boolean sync = ConnectionUtil.isConnected(context);

        if (sync) {
            final Account account = AccountUtils.getRtmAccount(context);

            // Check if we have an account and the sync has not been disabled
            // in between.
            sync = account != null && ContentResolver.getSyncAutomatically(account, Rtm.AUTHORITY);
        }

        return sync;
    }

    public final static boolean isSyncing(Context context) {
        final Account account = AccountUtils.getRtmAccount(context);

        return account != null && !ContentResolver.isSyncPending(account, Rtm.AUTHORITY)
                && ContentResolver.isSyncActive(account, Rtm.AUTHORITY);
    }

    /**
     * Loads the start time from the Sync database table and the interval from the settings.
     */
    public final static void schedulePeriodicSync(Context context) {
        final long interval = MolokoApp.getSettings(context).getSyncInterval();

        if (interval != Constants.SYNC_INTERVAL_MANUAL)
            SyncUtils.schedulePeriodicSync(context, interval);
    }

    /**
     * Loads the start time from the Sync database table.
     */
    public final static void schedulePeriodicSync(Context context, long interval) {
        long startUtc = System.currentTimeMillis();

        final ContentProviderClient client = context.getContentResolver()
                .acquireContentProviderClient(Sync.CONTENT_URI);

        if (client != null) {
            final Pair<Long, Long> lastSync = SyncProviderPart.getLastInAndLastOut(client);

            if (lastSync != null) {
                final long lastSyncIn = (lastSync.first != null) ? lastSync.first : Long.MAX_VALUE;
                final long lastSyncOut = (lastSync.second != null) ? lastSync.second : Long.MAX_VALUE;

                final long earliestLastSync = Math.min(lastSyncIn, lastSyncOut);

                // Ever synced?
                if (earliestLastSync != Long.MAX_VALUE) {
                    startUtc = earliestLastSync + interval;
                }
            }
        }

        MolokoApp.get(context).schedulePeriodicSync(startUtc, interval);
    }

    public final static <V> boolean hasChanged(V oldVal, V newVal) {
        return (oldVal == null && newVal != null) || (oldVal != null && (newVal == null || !oldVal.equals(newVal)));
    }

    public enum SyncResultDirection {
        NOTHING, LOCAL, SERVER
    }

    public final static <V> SyncResultDirection getSyncDirection(SyncProperties properties, String columnName,
            V serverValue, V localValue, Class<V> valueClass) {
        SyncResultDirection syncDir = SyncResultDirection.NOTHING;

        final Modification modification = properties.getModification(columnName);

        // Check if we should simply sync out and have a modification
        if (properties.serverModDate == null && modification != null) {
            syncDir = SyncResultDirection.SERVER;
        }

        else if (hasChanged(serverValue, localValue)) {
            // Check if the local value was modified
            if (modification != null) {
                // MERGE
                final V syncedValue = modification.getSyncedValue(valueClass);

                // Check if the server value has changed compared to the last synced value.
                if (hasChanged(syncedValue, serverValue)) {
                    // CONFLICT: Local and server element has changed.
                    // Let the modified date of the elements decide in which direction to sync.
                    //
                    // In case of equal dates we take the server value cause this
                    // value we have transferred already.
                    if (properties.serverModDate != null
                            && (properties.serverModDate.getTime() >= properties.localModDate.getTime()))
                        // LOCAL UPDATE: The server element was modified after the local value.
                        syncDir = SyncResultDirection.LOCAL;
                    else
                        // SERVER UPDATE: The local element was modified after the server element.
                        syncDir = SyncResultDirection.SERVER;
                } else
                    // SERVER UPDATE: The server value has not been changed since last sync,
                    // so use local modified value.
                    syncDir = SyncResultDirection.SERVER;
            }

            // LOCAL UPDATE: If the element has not locally changed, take the server version
            else {
                syncDir = SyncResultDirection.LOCAL;
            }
        }

        return syncDir;
    }

    public final static <T> void applyServerOperations(RtmProvider rtmProvider,
            List<? extends IServerSyncOperation<T>> serverOps, List<T> sortedServerElements,
            Comparator<? super T> cmp) throws ServiceException {
        for (IServerSyncOperation<T> serverOp : serverOps) {
            if (!(serverOp instanceof INoopSyncOperation)) {
                try {
                    final T result = serverOp.execute(rtmProvider);

                    if (result == null)
                        throw new ServiceException(-1, "ServerSyncOperation produced no result");

                    // If the set already contains an element in respect to the Comparator,
                    // then we update it by the new received.
                    final int pos = Collections.binarySearch(sortedServerElements, result, cmp);

                    if (pos >= 0) {
                        sortedServerElements.remove(pos);
                        sortedServerElements.add(pos, result);
                    } else {
                        sortedServerElements.add((-pos - 1), result);
                    }
                } catch (ServiceException e) {
                    MolokoApp.Log.e(SyncUtils.class, "Applying server operation failed", e);
                    throw e;
                }
            }
        }
    }
}