/*
* Copyright (C) 2010 Josh Guilfoyle <jasta@devtcg.org>
*
* 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 2, 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.
*/
package org.devtcg.five.provider;
import org.devtcg.five.service.SyncContext;
import org.devtcg.five.util.Stopwatch;
import android.content.Context;
import android.util.Log;
public abstract class AbstractSyncAdapter
{
private static final String TAG = "AbstractSyncAdapter";
/**
* Number of I/O errors that will be tolerated before we abort the sync with
* an error.
*/
private static int MAXIMUM_NETWORK_RETRIES = 3;
/**
* Array of retry intervals. This length of time is slept between each
* successive retry.
*/
private static final int[] NETWORK_RETRY_DELAY = new int[] {
5000, 30000, 50000
};
private final AbstractSyncProvider mProvider;
private final Context mContext;
public AbstractSyncAdapter(Context context, AbstractSyncProvider provider)
{
mContext = context;
mProvider = provider;
}
public Context getContext()
{
return mContext;
}
public abstract void getServerDiffs(SyncContext context, AbstractSyncProvider serverDiffs);
/**
* Run the main sync loop. Some effort was made to make this similar to
* Google's own sync engine for Android, and as such well generalized.
* My attempt has only partially succeeded and there is quite a bit of
* leak abstraction in this design taking advantage of the fact that we
* only have 1 provider to sync always.
*/
public void runSyncLoop(SyncContext context)
{
AbstractSyncProvider serverDiffs = mProvider.getSyncInstance();
int maxTries = context.numberOfTries + MAXIMUM_NETWORK_RETRIES + 1;
Stopwatch watch = new Stopwatch();
while (context.hasCanceled() == false && context.numberOfTries++ < maxTries)
{
if (context.observer != null)
context.observer.onStatusChanged("Downloading changes...");
Log.d(TAG, "Downloading server diffs...");
watch.start();
/*
* Download all changes since our last sync from the server.
*/
try {
getServerDiffs(context, serverDiffs);
} catch (RuntimeException e) {
/* Uncaught runtime exception, blank the sync status. */
if (context.observer != null)
context.observer.onStatusChanged(null);
/* Explode our process. */
throw e;
}
watch.stopAndDebugElapsed(TAG, "getServerDiffs");
if (context.hasCanceled() == true)
break;
if (context.hasError())
{
/*
* If we are going to retry, apply a short delay while we still
* have the wake lock. This is designed as a way to avoid the
* more expensive AlarmManager to schedule a retry in such a
* short window of time. If we fail up to our allowed retry
* limit we will be forced to reschedule another try later.
*/
if (context.numberOfTries < maxTries)
{
try {
int retryIndex = (MAXIMUM_NETWORK_RETRIES -
(maxTries - context.numberOfTries));
Thread.sleep(NETWORK_RETRY_DELAY[retryIndex]);
} catch (InterruptedException e) {}
}
continue;
}
/*
* No more network work left, go ahead and fold the temporary
* serverDiffs provider into the main one.
*/
if (!context.moreRecordsToGet)
{
if (context.observer != null)
context.observer.onStatusChanged("Merging changes...");
Log.d(TAG, "Downloaded records, merging...");
watch.start();
mProvider.merge(context, serverDiffs);
watch.stopAndDebugElapsed(TAG, "serverDiffs.merge");
if (!context.hasError())
Log.d(TAG, "Successfully merged " + context.getTotalRecordsProcessed() + " records!");
break;
}
}
serverDiffs.close();
if (context.hasCanceled() == true)
Log.i(TAG, "Sync canceled");
else if (context.hasError())
Log.i(TAG, "Sync aborted with errors, will try again later...");
else
{
Log.i(TAG, "Sync completed successfully, processed " +
context.numberOfDeletes + " deletes, " +
context.numberOfInserts + " inserts, and " +
context.numberOfUpdates + " updates");
serverDiffs.onDestroySyncInstance();
}
}
}