2016-04-19 03:18:45 +00:00
|
|
|
package org.gerweck.scalafx.util
|
|
|
|
|
|
|
|
import scala.concurrent._
|
2016-05-05 05:25:36 +00:00
|
|
|
import scala.util._
|
2016-04-19 03:18:45 +00:00
|
|
|
|
|
|
|
import org.log4s._
|
|
|
|
|
|
|
|
import scalafx.application.Platform.runLater
|
|
|
|
import scalafx.beans.property._
|
|
|
|
|
|
|
|
/** An [[scalafx.beans.value.ObservableValue]] that pulls its value from a future.
|
|
|
|
*
|
|
|
|
* @author Sarah Gerweck <sarah.a180@gmail.com>
|
|
|
|
*/
|
|
|
|
object FutureObservable {
|
|
|
|
private[this] val logger = getLogger
|
|
|
|
|
2016-07-30 03:44:00 +00:00
|
|
|
/** Construct an observable that gives the result of [[scala.concurrent.Future.value]] at all
|
|
|
|
* times.
|
|
|
|
*
|
|
|
|
* Like the underlying method, it gives `None` until the `Future` completes, after which it
|
|
|
|
* gives a `Some` of the `Try` that describes the calculation.
|
|
|
|
*/
|
|
|
|
def ofTryOption[A](future: Future[A])(implicit ec: ExecutionContext): ReadOnlyObjectProperty[Option[Try[A]]] = {
|
|
|
|
val startValue = future.value
|
|
|
|
val prop = ObjectProperty(startValue)
|
|
|
|
if (startValue.isEmpty) {
|
|
|
|
future onComplete { result =>
|
|
|
|
runLater {
|
|
|
|
prop.value = Some(result)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
prop
|
|
|
|
}
|
|
|
|
|
|
|
|
/* NOTE: All the other methods below ''could'' be derived from this one, but
|
|
|
|
* it's better to use as few JavaFX callbacks as possible since they have to
|
|
|
|
* be executed in the main UI thread.
|
|
|
|
*/
|
|
|
|
|
2016-04-19 03:18:45 +00:00
|
|
|
/** Construct an observable that gives the value of the future on success.
|
|
|
|
*
|
|
|
|
* Until the future completes successfully, the value will be that of
|
|
|
|
* `defaultValue`. If there is an error, the value will persist as
|
|
|
|
* `defaultValue`.
|
|
|
|
*
|
|
|
|
* If you want to change state in case of an error, I recommend you use
|
|
|
|
* [[scala.concurrent.Future.recover]] to choose the values that will be
|
|
|
|
* used in that case. The `defaultValue` is provided because `Future` has
|
|
|
|
* no equivalent mechanism for this mandatory functionality, but recovery
|
|
|
|
* is already a built-in feature.
|
|
|
|
*/
|
2016-07-30 03:48:09 +00:00
|
|
|
def withDefault[A](defaultValue: A)(future: Future[A])(implicit ec: ExecutionContext): ReadOnlyObjectProperty[A] = {
|
2016-05-05 05:25:36 +00:00
|
|
|
future.value match {
|
|
|
|
case Some(Success(a)) =>
|
|
|
|
ObjectProperty(a)
|
|
|
|
|
|
|
|
case Some(Failure(f)) =>
|
2016-07-30 03:43:48 +00:00
|
|
|
logFailure(f)
|
2016-05-05 05:25:36 +00:00
|
|
|
ObjectProperty(defaultValue)
|
|
|
|
|
|
|
|
case None =>
|
|
|
|
val prop = ObjectProperty[A](defaultValue)
|
2016-07-30 03:43:48 +00:00
|
|
|
future onComplete {
|
|
|
|
case Success(a) =>
|
|
|
|
runLater {
|
|
|
|
prop.value = a
|
|
|
|
}
|
|
|
|
case Failure(f) =>
|
|
|
|
logFailure(f)
|
2016-05-05 05:25:36 +00:00
|
|
|
}
|
|
|
|
prop
|
2016-04-19 03:18:45 +00:00
|
|
|
}
|
|
|
|
}
|
2016-06-04 07:30:04 +00:00
|
|
|
|
2016-07-30 03:48:09 +00:00
|
|
|
@deprecated("Use withDefault. `Apply` will be either removed or repurposed in a future release.", "0.10.2")
|
|
|
|
def apply[A](defaultValue: A)(future: Future[A])(implicit ec: ExecutionContext) = withDefault(defaultValue)(future)
|
|
|
|
|
2016-07-30 03:43:48 +00:00
|
|
|
/** Construct an observable that gives `None` until the `Future` completes successfully, after
|
|
|
|
* which the `Option` contains the successful result.
|
|
|
|
*
|
|
|
|
* This method does not allow you to differentiate between a failure and a calculation
|
|
|
|
* that is still running. If you need to differentiate these, you can either use
|
|
|
|
* [[scala.concurrent.Future.recover]] or use [[ofTryOption]] instead.
|
|
|
|
*/
|
2016-06-04 07:30:04 +00:00
|
|
|
def ofSuccessOption[A](future: Future[A])(implicit ec: ExecutionContext): ReadOnlyObjectProperty[Option[A]] = {
|
|
|
|
future.value match {
|
|
|
|
case Some(Success(a)) =>
|
|
|
|
ObjectProperty(Some(a))
|
|
|
|
|
|
|
|
case Some(Failure(f)) =>
|
2016-07-30 03:43:48 +00:00
|
|
|
logFailure(f)
|
2016-06-04 07:30:04 +00:00
|
|
|
ObjectProperty(None)
|
|
|
|
|
|
|
|
case None =>
|
|
|
|
val prop = ObjectProperty[Option[A]](None)
|
2016-07-30 03:43:48 +00:00
|
|
|
future onComplete {
|
|
|
|
case Success(a) =>
|
|
|
|
runLater {
|
|
|
|
prop.value = Some(a)
|
|
|
|
}
|
|
|
|
case Failure(f) =>
|
|
|
|
logFailure(f)
|
2016-06-04 07:30:04 +00:00
|
|
|
}
|
|
|
|
prop
|
|
|
|
}
|
|
|
|
}
|
2016-07-30 03:43:48 +00:00
|
|
|
|
|
|
|
private[this] def logFailure(f: Throwable): Unit = {
|
|
|
|
logger.debug(s"Got failure from FutureObservable's result: $f")
|
|
|
|
}
|
2016-04-19 03:18:45 +00:00
|
|
|
}
|