diff --git a/astrid/src/main/java/com/todoroo/astrid/gtasks/sync/GtasksSyncV2Provider.java b/astrid/src/main/java/com/todoroo/astrid/gtasks/sync/GtasksSyncV2Provider.java index 63b119346..c042a4645 100644 --- a/astrid/src/main/java/com/todoroo/astrid/gtasks/sync/GtasksSyncV2Provider.java +++ b/astrid/src/main/java/com/todoroo/astrid/gtasks/sync/GtasksSyncV2Provider.java @@ -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 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(); } } diff --git a/astrid/src/main/java/com/todoroo/astrid/helper/ProgressBarSyncResultCallback.java b/astrid/src/main/java/com/todoroo/astrid/helper/ProgressBarSyncResultCallback.java index c8aaf824b..ea89b43aa 100644 --- a/astrid/src/main/java/com/todoroo/astrid/helper/ProgressBarSyncResultCallback.java +++ b/astrid/src/main/java/com/todoroo/astrid/helper/ProgressBarSyncResultCallback.java @@ -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 } diff --git a/astrid/src/main/java/com/todoroo/astrid/service/SyncResultCallbackWrapper.java b/astrid/src/main/java/com/todoroo/astrid/service/SyncResultCallbackWrapper.java index f783a1a1d..b972beea8 100644 --- a/astrid/src/main/java/com/todoroo/astrid/service/SyncResultCallbackWrapper.java +++ b/astrid/src/main/java/com/todoroo/astrid/service/SyncResultCallbackWrapper.java @@ -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. + } + + } + + } + }