Skip to content

Instantly share code, notes, and snippets.

@timusus
Created December 20, 2018 00:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timusus/65c65a8073dee0149dbce699bc6ff107 to your computer and use it in GitHub Desktop.
Save timusus/65c65a8073dee0149dbce699bc6ff107 to your computer and use it in GitHub Desktop.
/**
* 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