Better progress bar for syncing

Made changes so that a progress bar cannot jump back (especially while
syncing) and changed the way synchronising code presents progress so
that it appears more representative.

Closes #46
pull/73/head
Allan Crooks 12 years ago committed by Alex Baker
parent 938121f75e
commit e5f66dff9d

@ -37,6 +37,7 @@ import com.todoroo.astrid.gtasks.api.GtasksInvoker;
import com.todoroo.astrid.gtasks.auth.GtasksTokenValidator;
import com.todoroo.astrid.service.AstridDependencyInjector;
import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.service.SyncResultCallbackWrapper;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.sync.SyncResultCallback;
import com.todoroo.astrid.sync.SyncV2Provider;
@ -106,7 +107,7 @@ public class GtasksSyncV2Provider extends SyncV2Provider {
final boolean isImport = false;
callback.started();
callback.incrementMax(100);
callback.incrementMax(20);
gtasksPreferenceService.recordSyncStart();
@ -114,7 +115,7 @@ public class GtasksSyncV2Provider extends SyncV2Provider {
new Thread(new Runnable() {
@Override
public void run() {
callback.incrementProgress(50);
callback.incrementProgress(1); // 5%
String authToken = getValidatedAuthToken();
final GtasksInvoker invoker = new GtasksInvoker(authToken);
try {
@ -130,17 +131,29 @@ public class GtasksSyncV2Provider extends SyncV2Provider {
finishSync(callback);
return;
}
callback.incrementMax(25 * lists.length);
callback.incrementProgress(1); // 10%
final SyncResultCallbackWrapper.Partial callback0;
callback0 = new SyncResultCallbackWrapper.Partial(
callback, 2, 16
);
callback0.incrementMax(lists.length * 3);
final AtomicInteger finisher = new AtomicInteger(lists.length);
for (final StoreObject list : lists) {
new Thread(new Runnable() {
@Override
public void run() {
synchronizeListHelper(list, invoker, manual, handler, callback, isImport);
callback.incrementProgress(25);
synchronizeListHelper(list, invoker, manual, handler, callback0, isImport);
callback.incrementProgress(3);
if (finisher.decrementAndGet() == 0) {
pushUpdated(invoker, callback);
callback0.incrementProgress(8);
SyncResultCallback callback1 = new SyncResultCallbackWrapper.Rescaled(
callback0, 1, callback0.getRemainderSize()
);
pushUpdated(invoker, callback1);
callback0.incrementProgress(8);
finishSync(callback);
}
}
@ -155,7 +168,7 @@ public class GtasksSyncV2Provider extends SyncV2Provider {
TodorooCursor<Task> queued = taskService.query(Query.select(Task.PROPERTIES).
join(Join.left(Metadata.TABLE, Criterion.and(MetadataCriteria.withKey(GtasksMetadata.METADATA_KEY), Task.ID.eq(Metadata.TASK)))).where(
Criterion.or(Task.MODIFICATION_DATE.gt(GtasksMetadata.LAST_SYNC),
Criterion.and(Task.USER_ID.neq(Task.USER_ID_SELF), GtasksMetadata.ID.isNotNull()),
Criterion.and(Task.USER_ID.neq(Task.USER_ID_SELF), GtasksMetadata.ID.isNotNull()),
Metadata.KEY.isNull())));
callback.incrementMax(queued.getCount() * 10);
try {
@ -191,21 +204,21 @@ public class GtasksSyncV2Provider extends SyncV2Provider {
final boolean isImport = false;
callback.started();
callback.incrementMax(100);
callback.incrementMax(8);
new Thread(new Runnable() {
@Override
public void run() {
callback.incrementProgress(50);
callback.incrementProgress(4);
try {
String authToken = getValidatedAuthToken();
callback.incrementProgress(12);
callback.incrementProgress(1);
gtasksSyncService.waitUntilEmpty();
callback.incrementProgress(13);
callback.incrementProgress(1);
final GtasksInvoker service = new GtasksInvoker(authToken);
synchronizeListHelper(gtasksList, service, manual, null, callback, isImport);
} finally {
callback.incrementProgress(25);
callback.incrementProgress(2);
callback.finished();
}
}

@ -77,13 +77,58 @@ public class ProgressBarSyncResultCallback extends SyncResultCallbackAdapter {
}
}
/**
* This method helps rescaling a progress bar to increase the number of data points that form
* the progress bar, but without reducing the % of progress already done.
*
* This is done by increasing the length of the progress bar and the amount of progress whilst
* keeping the remaining amount of progress left remains the same. This code makes the
* presumption that progress isn't reversed and only increases.
*
* @param p The current level of progress that has been made.
* @param m The current total of data points which makes up the progress bar (the maximum).
* @param x The number of data points to increase the progress bar by.
* @return An integer array of two values - the first is the new value for progress and the
* second is the new value for the maximum.
*/
public static int[] rescaleProgressValues(int p, int m, int x) {
/**
* If we have no progress yet, then a straight-forward increment of the max will suffice
* here (and is necessary, the below code will break otherwise).
*/
if (p == 0) {return new int[] {p, m + x};}
/**
* This is more of an error in caller code. We shouldn't be asked to rescale a progress
* bar that has currently reached 100%, but hasn't been declared as "finished". But in
* case this happens, and since we can't correctly rescale a progress bar that's at 100%,
* we just allow it to move back.
*/
if (p == m) {return new int[] {p, m + x};}
// This is what does the magic!
double m2d = ((double)(m*(m+x-p))) / (m-p);
int m2 = (int)Math.floor(m2d);
int p2 = m2 + p - m - x;
return new int[] {p2, m2};
}
private void rescaleProgressBarBy(int x) {
int[] new_values = rescaleProgressValues(progressBar.getProgress(), progressBar.getMax(), x);
if (new_values[1] != -1) {progressBar.setMax(new_values[1]);}
if (new_values[0] != -1) {progressBar.setProgress(new_values[0]);}
}
@Override
public void incrementMax(final int incrementBy) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
progressBar.setMax(progressBar.getMax() + incrementBy);
rescaleProgressBarBy(incrementBy);
} catch (Exception e) {
// ignore
}

@ -2,6 +2,7 @@ package com.todoroo.astrid.service;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.helper.ProgressBarSyncResultCallback;
import com.todoroo.astrid.sync.SyncResultCallback;
import com.todoroo.astrid.widget.TasksWidget;
@ -47,4 +48,143 @@ public class SyncResultCallbackWrapper implements SyncResultCallback {
}
}
/**
* This class can be used if you want to claim a relative proportion of the SyncResultCallback,
* but you may also want to increase the maximum size of data points as well. For example, there
* may be 10 data points, and you want to make use of half of them to indicate that once you
* are finished, you are half way through. But if you increase the maximum number of data
* points, the remainder that you're not interacting with will need to increase by the same
* proportion so that you still interact with half of the data points.
*
* Once you wrap this class around the original SyncResultCallback object and have finished
* using it, you then invoke the getRemainderSize method afterwards to see how many data points are
* left for you to use. You can then use the Rescaled wrapper to simplify caller logic afterwards.
*/
public static class Partial extends SyncResultCallbackWrapper {
protected final double factor;
protected int fragment_size;
protected int total_size;
/**
* Wraps a SyncResultCallback object.
*
* @param wrap Original object to wrap.
* @param fragment_size Number of data points that you want to interact with.
* @param total_size Total number of data points remaining and available in the original object.
*/
public Partial(SyncResultCallback wrap,
int fragment_size, int total_size) {
super(wrap);
this.factor = Math.ceil((double)total_size / fragment_size);
this.fragment_size = fragment_size;
this.total_size = total_size;
}
@Override
public void incrementMax(int incrementBy) {
this.fragment_size += incrementBy;
int new_total = (int)(this.factor * this.fragment_size);
super.incrementMax(new_total - total_size);
this.total_size = new_total;
}
public int getRemainderSize() {
return this.total_size - this.fragment_size;
}
}
/**
* Given a SyncResultCallback object with a certain number of data points, treat it as if it
* had a different number of data points, and allow the underlying wrapped object to receive a
* proportionally equivalent amount of data points.
*/
public static class Rescaled extends SyncResultCallbackWrapper {
protected int input_size;
protected int actual_size;
protected int input_count;
protected int actual_count;
public Rescaled(SyncResultCallback wrap,
int input_size, int actual_size) {
super(wrap);
if (input_size <= 0) throw new IllegalArgumentException("input_size is not +ve");
if (actual_size <= 0) throw new IllegalArgumentException("actual size is not +ve");
this.input_size = input_size;
this.actual_size = actual_size;
this.input_count = 0;
this.actual_count = 0;
// If we have more input data points than output, we'll increase the number of data
// points in the underlying one, because otherwise incremental updates fed into us
// might not show up in the underlying object.
if (actual_size < input_size) {
super.incrementMax(input_size - actual_size);
this.actual_size = input_size;
}
}
@Override
public void incrementProgress(int incrementBy) {
// Shortcut if we're no longer rescaling.
if (input_size == actual_size) {
super.incrementProgress(incrementBy);
return;
}
int new_input_count = input_count + incrementBy;
int new_actual_count = (new_input_count * actual_size) / input_size;
if (new_actual_count > actual_count) {
// This should always be the case, but we just sanity check it.
super.incrementProgress(new_actual_count - actual_count);
}
this.input_count = new_input_count;
this.actual_count = new_actual_count;
}
@Override
public void incrementMax(int incrementBy) {
// Shortcut if we're not longer rescaling.
if (input_size == actual_size) {
super.incrementMax(incrementBy);
return;
}
// If we increase the maximum used by the input, we want to rescale the values such
// that the progress (proportionally) remains the same - otherwise we'll appear to
// have made backward progress on the input side, which we can't indicate to the
// underlying object.
int[] rescaled_input = ProgressBarSyncResultCallback.rescaleProgressValues(
input_count, input_size, incrementBy
);
this.input_count = rescaled_input[0];
this.input_size = rescaled_input[1];
// No need to rescale internally if no data points exist.
if (input_count == 0) {return;}
// The point of this class is to increase the size of input so that it increases the
// amount of data points sent to the underlying object. Once the input size becomes the
// same as underlying size, we don't need to do anything other than send the same
// information to the underlying class.
//
// So:
// If input remains smaller than actual, we do nothing here.
// If input remains equal to actual, we just pass the same data along.
// If input goes from smaller to bigger, we need to catch up to make it equal.
if (input_count > actual_count) {
super.incrementMax(input_count - actual_count);
this.actual_count = input_count; // Switches off rescaling.
}
}
}
}

Loading…
Cancel
Save