Skip to content

Commit

Permalink
Cache loaded activities in UserContextService to improve app response…
Browse files Browse the repository at this point in the history
… when switching between states.
  • Loading branch information
OndrejSpanel committed Oct 23, 2019
1 parent b893ebc commit db6e4ea
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,80 @@ package com.github.opengrabeso.mixtio
package frontend
package services

import common.model.UserContext
import java.time.{ZoneOffset, ZonedDateTime}

import common.model._
import common.Util._

import scala.concurrent.{ExecutionContext, Future}
import UserContextService._

object UserContextService {
final val normalCount = 15

case class LoadedActivities(staged: Seq[ActivityHeader], strava: Seq[ActivityHeader])

class UserContextData(userId: String, authCode: String, rpc: rest.RestAPI)(implicit ec: ExecutionContext) {
var loaded = Option.empty[(Boolean, Future[LoadedActivities])]
var context = UserContext(userId, authCode)

def userAPI: rest.UserRestAPI = rpc.userAPI(context.userId, context.authCode)

private def notBeforeByStrava(showAll: Boolean, stravaActivities: Seq[ActivityHeader]): ZonedDateTime = {
if (showAll) ZonedDateTime.now().withZoneSameInstant(ZoneOffset.UTC) minusMonths 24
else stravaActivities.map(a => a.id.startTime).min
}

private def doLoadActivities(showAll: Boolean): Future[LoadedActivities] = {
println(s"loadActivities showAll=$showAll")

userAPI.lastStravaActivities(normalCount * 2).flatMap { allActivities =>
val stravaActivities = allActivities.take(normalCount)
val notBefore = notBeforeByStrava(showAll, stravaActivities)

val ret = userAPI.stagedActivities(notBefore).map { stagedActivities =>
LoadedActivities(stagedActivities, allActivities)
}
loaded = Some(showAll, ret)
ret
}
}

def loadCached(level: Boolean): Future[LoadedActivities] = {
if (loaded.isEmpty || loaded.exists(!_._1 && level)) {
doLoadActivities(level)
} else {
loaded.get._2
}
}
}
}

class UserContextService(rpc: rest.RestAPI)(implicit ec: ExecutionContext) {
private var userContext: Option[UserContext] = None

private var userData: Option[UserContextData] = None

def login(userId: String, authCode: String): UserContext = {
val ctx = UserContext(userId, authCode)
userContext = Some(ctx)
ctx
val ctx = new UserContextData(userId, authCode, rpc)
userData = Some(ctx)
ctx.context
}
def logout(): Future[UserContext] = {
userContext.flatMap { ctx =>
api.map(_.logout.map(_ => ctx))
userData.flatMap { ctx =>
api.map(_.logout.map(_ => ctx.context))
}.getOrElse(Future.failed(new UnsupportedOperationException))
}

def userName: Option[Future[String]] = api.map(_.name)
def userId: Option[String] = userContext.map(_.userId)
def userId: Option[String] = userData.map(_.context.userId)

def loadCached(level: Boolean): Future[LoadedActivities] = {
userData.get.loadCached(level)
}


// TODO: double check authCode usage is safe here (it should be, we are frontend only here)
def api: Option[rest.UserRestAPI] = userContext.map { ctx =>
rpc.userAPI(ctx.userId, ctx.authCode)
def api: Option[rest.UserRestAPI] = userData.map { data =>
rpc.userAPI(data.context.userId, data.context.authCode)
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.github.opengrabeso.mixtio
package frontend.views

import com.github.opengrabeso.mixtio.common.model.{FileId, UploadProgress}
import com.github.opengrabeso.mixtio.frontend.views.select.PagePresenter.delay
import common.model._

import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.scalajs.js

abstract class PendingUploads[ActId](implicit ec: ExecutionContext) {
var pending = Map.empty[String, Set[ActId]]
Expand All @@ -13,6 +13,15 @@ abstract class PendingUploads[ActId](implicit ec: ExecutionContext) {

def sendToStrava(fileIds: Seq[ActId]): Future[Seq[(ActId, String)]]

def delay(milliseconds: Int): Future[Unit] = {
val p = Promise[Unit]()
js.timers.setTimeout(milliseconds) {
p.success(())
}
p.future
}


def startUpload(api: rest.UserRestAPI, fileIds: Seq[ActId]) = {

val setOfFileIds = fileIds.toSet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import io.udash.bootstrap.button.UdashButton
import io.udash.component.ComponentId
import io.udash.css._
import common.css._
import org.scalajs.dom
import org.scalajs.dom.raw.HTMLElement

import scala.concurrent.ExecutionContext
Expand All @@ -25,6 +26,7 @@ object Root {
userContextService: services.UserContextService,
application: Application[RoutingState]
)(implicit ec: ExecutionContext) extends Presenter[RootState.type] {

// start the login
login()

Expand Down Expand Up @@ -59,6 +61,10 @@ object Root {
}
}
}

def gotoMain() = {
application.goTo(SelectPageState)
}
}


Expand Down Expand Up @@ -93,7 +99,13 @@ object Root {
td(
table(
tbody(
tr(td(a(href := "/", appName))),
tr(td(a(
href := "/", appName,
onclick :+= {_: dom.Event =>
presenter.gotoMain()
true
}
))),
tr(td(
"Athlete:",
produce(userId) { s =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,11 @@ import routing._
import io.udash._

import scala.concurrent.{ExecutionContext, Future, Promise}
import PagePresenter._
import org.scalajs.dom.File
import services.UserContextService

import scala.scalajs.js
import scala.util.{Failure, Success}

object PagePresenter {
case class LoadedActivities(staged: Seq[ActivityHeader], strava: Seq[ActivityHeader])

// TODO: move to some Utils
def delay(milliseconds: Int): Future[Unit] = {
val p = Promise[Unit]()
js.timers.setTimeout(milliseconds) {
p.success(())
}
p.future
}

}

/** Contains the business logic of this view. */
class PagePresenter(
model: ModelProperty[PageModel],
Expand All @@ -43,60 +28,24 @@ class PagePresenter(
loadActivities(p)
}

var loaded = Option.empty[(Boolean, Future[LoadedActivities])]

final private val normalCount = 15


private def notBeforeByStrava(showAll: Boolean, stravaActivities: Seq[ActivityHeader]): ZonedDateTime = {
if (showAll) ZonedDateTime.now().withZoneSameInstant(ZoneOffset.UTC) minusMonths 24
else stravaActivities.map(a => a.id.startTime).min
}

def doLoadActivities(showAll: Boolean): Future[LoadedActivities] = {
println(s"loadActivities showAll=$showAll")
model.subProp(_.loading).set(true)
model.subProp(_.activities).set(Nil)

userService.api match {
case Some(userAPI) =>
userAPI.lastStravaActivities(normalCount * 2).flatMap { allActivities =>
val stravaActivities = allActivities.take(normalCount)
val notBefore = notBeforeByStrava(showAll, stravaActivities)

val ret = userAPI.stagedActivities(notBefore).map { stagedActivities =>
LoadedActivities(stagedActivities, allActivities)
}
loaded = Some(showAll, ret)
ret
}
case None =>
Future.failed(new NoSuchElementException)

}
}
def loadActivities(showAll: Boolean) = {
val load = userService.loadCached(showAll)

private def loadCached(level: Boolean): Future[LoadedActivities] = {
println(s"loadCached $level")
if (loaded.isEmpty || loaded.exists(!_._1 && level)) {
doLoadActivities(level)
} else {
loaded.get._2
if (!load.isCompleted) {
// if not completed immediately, show as pending
model.subProp(_.loading).set(true)
model.subProp(_.activities).set(Nil)
}
}

def loadActivities(showAll: Boolean) = {
val load = loadCached(showAll)

for (LoadedActivities(stagedActivities, allStravaActivities) <- load) {
for (UserContextService.LoadedActivities(stagedActivities, allStravaActivities) <- load) {
println(s"loadActivities loaded staged: ${stagedActivities.size}, Strava: ${allStravaActivities.size}")

def filterListed(activity: ActivityHeader, strava: Option[ActivityHeader]) = showAll || strava.isEmpty
def findMatchingStrava(ids: Seq[ActivityHeader], strava: Seq[ActivityHeader]): Seq[(ActivityHeader, Option[ActivityHeader])] = {
ids.map( a => a -> strava.find(_.id isMatching a.id))
}

val (stravaActivities, oldStravaActivities) = allStravaActivities.splitAt(normalCount)
val (stravaActivities, oldStravaActivities) = allStravaActivities.splitAt(UserContextService.normalCount)
val neverBefore = alwaysIgnoreBefore(stravaActivities.map(_.id))

// without "withZoneSameInstant" the resulting time contained strange [SYSTEM] zone suffix
Expand Down

0 comments on commit db6e4ea

Please sign in to comment.