Created
December 20, 2018 00:15
-
-
Save timusus/65c65a8073dee0149dbce699bc6ff107 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Executes a unit of work at a specified interval. | |
* | |
* An attempt threshold can be specified. If the number of attempts to execute the work exceeds this threshold, `didExceedAttemptThreshold()` is called and the timer is | |
* invalidated. | |
* | |
* An error threshold can also be specified. If the number of errors exceeds this threshold, `didExceedErrorThreshold()` is called, and the latest error is passed in. | |
* | |
* Note: This class uses a Timer to execute the work, ensure to call `stop()` when no longer interested in the result, or a leak may occur. | |
* | |
* @param interval [Long] time between attempts, in milliseconds. Note: An attempt will not be made while another attempt is in-flight | |
* @param attemptThreshold [Int] the number of attempts before giving up and calling [didExceedAttemptThreshold]] (0 = indefinite) (defaults to 0) | |
* @param errorThreshold [Int] the number of errors before giving up and calling [didExceedErrorThreshold] (0 = indefinite) (defaults to 0) | |
* | |
*/ | |
open class AttemptEngine(private val interval: Long, private val attemptThreshold: Int = 0, private val errorThreshold: Int = 0) { | |
private var timer: Disposable? = null | |
private var attemptCount = 1 | |
private var errorCount = 1 | |
private var attemptInProgress = false | |
/** | |
* Starts a timer, which will call the `execute()` function every `interval`, until the attempt or error threshold is exceeded. | |
* | |
* @param immediate whether to `execute()` immediately, as well as at every `interval`. | |
* | |
* Note: Call `stop()` to invalidate the timer and prevent leaks. | |
*/ | |
fun start(immediate: Boolean = false) { | |
timer?.dispose() | |
timer = Observable.interval(if (immediate) 0L else interval, interval, MILLISECONDS) | |
.observeOn(AndroidSchedulers.mainThread()) | |
.subscribe { | |
if (attemptThreshold in 1..attemptCount) { | |
timer?.dispose() | |
didExceedAttemptThreshold() | |
return@subscribe | |
} | |
if (attemptInProgress) { | |
return@subscribe | |
} | |
attemptInProgress = true | |
execute { latestError -> | |
latestError?.let { error -> | |
if (errorThreshold in 1..errorCount) { | |
timer?.dispose() | |
didExceedErrorThreshold(error) | |
} | |
errorCount += 1 | |
} | |
attemptInProgress = false | |
} | |
attemptCount += 1 | |
} | |
} | |
/** | |
* Execute a unit of work, optionally returning an error. | |
* | |
* Note: Ensure to call completion() once the work has completed, in order to mark the work as no longer in progress. | |
*/ | |
open fun execute(completion: (Error?) -> (Unit)) { | |
} | |
/** | |
* Called when the number of attempts exceeds the attempt threshold. | |
*/ | |
open fun didExceedAttemptThreshold() { | |
} | |
/** | |
* @param latestError The most recent `Error` returned by `execute()` | |
*/ | |
open fun didExceedErrorThreshold(latestError: Error) { | |
} | |
/** | |
* Invalidates and destroys the timer | |
*/ | |
fun stop() { | |
timer?.dispose() | |
} | |
/** | |
* Invalidates and destroys the timer, and resets the attempt and error count to 0. | |
*/ | |
fun reset() { | |
stop() | |
attemptCount = 0 | |
errorCount = 0 | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment