From 108ce27671390deda200492975d2c72faecc348e Mon Sep 17 00:00:00 2001 From: kolena Date: Fri, 14 Dec 2018 18:12:40 +0100 Subject: [PATCH] Better updater Some fixes --- app/AppModule.scala | 5 +- app/lib/App.scala | 2 +- app/lib/FilesHandler.scala | 5 +- app/updater/GithubConnector.scala | 78 ++++++++++++++++++++++++------- app/updater/Updater.scala | 22 +++++++-- conf/reference.conf | 4 +- 6 files changed, 86 insertions(+), 30 deletions(-) diff --git a/app/AppModule.scala b/app/AppModule.scala index 5dca9e3..7d3c05e 100644 --- a/app/AppModule.scala +++ b/app/AppModule.scala @@ -16,6 +16,7 @@ import net.codingwell.scalaguice.ScalaModule import org.apache.commons.lang3.SystemUtils import org.http4s.Uri import org.http4s.client.blaze.Http1Client +import org.http4s.client.middleware.FollowRedirect import play.api.{Configuration, Environment} import scalikejdbc._ import scalikejdbc.config.DBs @@ -78,14 +79,14 @@ class AppModule(environment: Environment, configuration: Configuration) bind[GithubConnector].toInstance { new GithubConnector( - Http1Client[Task]().runSyncUnsafe(Duration.Inf), + FollowRedirect(5)(Http1Client[Task]().runSyncUnsafe(Duration.Inf)), Uri.unsafeFromString(config.getString("updater.releasesUrl")), App.version, blockingScheduler ) } - bind[Duration].annotatedWithName("updaterCheckPeriod").toInstance(config.getDuration("updater.checkPeriod").toMillis.millis) + bind[FiniteDuration].annotatedWithName("updaterCheckPeriod").toInstance(config.getDuration("updater.checkPeriod").toMillis.millis) bind[Monitor].annotatedWithName("FilesHandler").toInstance(rootMonitor.named("fileshandler")) diff --git a/app/lib/App.scala b/app/lib/App.scala index 69ac539..856f8ed 100644 --- a/app/lib/App.scala +++ b/app/lib/App.scala @@ -38,7 +38,7 @@ class App @Inject()(backupSetsExecutor: BackupSetsExecutor, updater: Updater)(li } object App { - final val versionStr: String = "0.1.0" + final val versionStr: String = "0.1.2" final val version: AppVersion = AppVersion(versionStr).getOrElse(throw new IllegalArgumentException("Could not parse versionStr")) type Result[A] = EitherT[Task, AppException, A] diff --git a/app/lib/FilesHandler.scala b/app/lib/FilesHandler.scala index 27905b9..fb65684 100644 --- a/app/lib/FilesHandler.scala +++ b/app/lib/FilesHandler.scala @@ -216,10 +216,7 @@ class FilesHandler @Inject()(cloudConnector: CloudConnector, private def shouldUpload(file: File): Result[Boolean] = { checkFileUpdated(file) .flatMap { - case false => - logger.debug(s"File $file not updated, is 'upload_same_content' turned on?") - settings.uploadSameContent - + case false => settings.uploadSameContent case true => pureResult(true) } } diff --git a/app/updater/GithubConnector.scala b/app/updater/GithubConnector.scala index b8aa7ca..95b6b94 100644 --- a/app/updater/GithubConnector.scala +++ b/app/updater/GithubConnector.scala @@ -7,6 +7,7 @@ import better.files.File import cats.data.EitherT import cats.effect.Effect import cats.syntax.all._ +import com.typesafe.scalalogging.StrictLogging import io.circe.Decoder import lib.App._ import lib.AppException @@ -14,18 +15,24 @@ import lib.AppException._ import monix.eval.Task import monix.execution.Scheduler import org.http4s.client.Client -import org.http4s.{Status, Uri} +import org.http4s.headers.`Content-Length` +import org.http4s.{Response, Status, Uri} import updater.GithubConnector._ import scala.concurrent.ExecutionContext -class GithubConnector(httpClient: Client[Task], uri: Uri, appVersion: AppVersion, blockingScheduler: Scheduler)(implicit F: Effect[Task]) { +class GithubConnector(httpClient: Client[Task], uri: Uri, appVersion: AppVersion, blockingScheduler: Scheduler)(implicit F: Effect[Task]) + extends StrictLogging { def checkUpdate: Result[Option[Release]] = { EitherT { + logger.debug(s"Checking update at $uri") + httpClient.get(uri) { resp => resp.bodyAsText.compile.toList.map(_.mkString.trim).flatMap { strBody => resp.status match { - case Status.Ok => Task.now(parse(strBody)) + case Status.Ok => + logger.debug(s"Updater check response:\n$strBody") + Task.now(parse(strBody)) case s => Task.now(Left(InvalidResponseException(s.code, strBody, "Could not download releases"): AppException)) } } @@ -43,25 +50,62 @@ class GithubConnector(httpClient: Client[Task], uri: Uri, appVersion: AppVersion def download(asset: Asset): Result[File] = { EitherT { + logger.debug(s"Downloading update from ${asset.browserDownloadUrl}") + httpClient .get(asset.browserDownloadUrl) { resp => - val file = File.temporaryFile("rbackup", s"update-${asset.name}").get - implicit val ec: ExecutionContext = blockingScheduler - - resp.body - .through(fs2.io.writeOutputStreamAsync(Task { - file.newOutputStream - }.executeOnScheduler(blockingScheduler))) - .compile - .drain - .map(_ => Right(file): Either[AppException, File]) - .onErrorRecover { - case e: IOException => Left(FileException("Could not download update", e)) - } - } + resp.status match { + case Status.Ok => + `Content-Length`.from(resp.headers) match { + case Some(clh) => + if (clh.length == asset.size) { + download(asset, resp) + } else { + resp.bodyAsText.compile.toList.map(_.mkString).map { body => + logger.debug(s"Content-Length header doesn't match asset size - ${clh.length} != ${asset.size}\n$body") + Left(UpdateException("Content-Length header doesn't match asset size")) + } + } + + case None => + resp.bodyAsText.compile.toList.map(_.mkString).map { body => + logger.debug(s"Missing Content-Length header, expected ${asset.size}\n$body") + Left(UpdateException("Missing Content-Length header")) + } + } + + case s => + resp.bodyAsText.compile.toList.map(_.mkString).map { body => + logger.debug(s"Missing Content-Length header, expected ${asset.size}\n$body") + Left(InvalidResponseException(s.code, body, "Update failure")) + } + } + } } } + + private def download(asset: Asset, resp: Response[Task]): Task[Either[AppException, File]] = { + val file = File.temporaryFile("rbackup", s"update-${asset.name}").get + implicit val ec: ExecutionContext = blockingScheduler + + resp.body + .through(fs2.io.file.writeAllAsync(file.path)) + .compile + .drain + .map { _ => + if (file.size == asset.size) { + logger.debug(s"Update downloaded to $file") + Right(file): Either[AppException, File] + } else { + logger.debug(s"Size of downloaded file mismatch - ${file.size} != ${asset.size}") + Left(UpdateException("Size of downloaded file mismatch")) + } + } + .onErrorRecover { + case e: IOException => Left(FileException("Could not download update", e)) + } + } } object GithubConnector { diff --git a/app/updater/Updater.scala b/app/updater/Updater.scala index 61f03cf..73e4a2f 100644 --- a/app/updater/Updater.scala +++ b/app/updater/Updater.scala @@ -3,10 +3,12 @@ package updater import java.util.concurrent.atomic.AtomicBoolean import better.files.File +import cats.data.EitherT import com.typesafe.scalalogging.StrictLogging import javax.inject.{Inject, Named, Singleton} import lib.App._ import lib.AppException.UpdateException +import monix.eval.Task import monix.execution.{Cancelable, Scheduler} import updater.GithubConnector.Release @@ -21,7 +23,7 @@ class Updater @Inject()(connector: GithubConnector, private val updateRunning = new AtomicBoolean(false) def start: Cancelable = { - logger.info("Started updates checker") + logger.info(s"Started updates checker, will check every $checkPeriod") scheduler.scheduleAtFixedRate(1.seconds, checkPeriod) { connector.checkUpdate @@ -36,13 +38,24 @@ class Updater @Inject()(connector: GithubConnector, logger.debug("Didn't find update for current version") pureResult(()) } + .recoverWith { + case ae => + updateRunning.set(false) + logger.warn("Could not download update", ae) + EitherT.leftT[Task, Unit](ae) + } .value .onErrorRecover { case e: java.net.ConnectException => updateRunning.set(false) - logger.warn("Could not check for update", e) + logger.warn("Could not download update", e) Left(UpdateException("Could not update the app", e)) + case e: UpdateException => + updateRunning.set(false) + logger.warn("Could not download update", e) + Left(e) + case NonFatal(e) => updateRunning.set(false) logger.warn("Unknown error while updating the app", e) @@ -61,7 +74,7 @@ class Updater @Inject()(connector: GithubConnector, val dirWithUpdate = file.unzipTo(File(s"update-${release.tagName}")) // the data are in subfolder, move them up dirWithUpdate.children.next().children.foreach { sub => - logger.debug(s"Moving $sub to $dirWithUpdate") + logger.trace(s"Moving $sub to $dirWithUpdate") sub.moveToDirectory(dirWithUpdate) } @@ -77,7 +90,8 @@ class Updater @Inject()(connector: GithubConnector, private def downloadUpdate(release: Release): Result[File] = { release.assets.find(_.name == s"rbackup-client-${release.tagName}.zip") match { - case Some(asset) => connector.download(asset) + case Some(asset) => + connector.download(asset) case None => logger.warn(s"Wanted to update, but didn't found proper asset in release '${release.name}'") failedResult(UpdateException(s"Could not found proper asset in release '${release.name}'")) diff --git a/conf/reference.conf b/conf/reference.conf index f2075e0..d7176ba 100644 --- a/conf/reference.conf +++ b/conf/reference.conf @@ -16,8 +16,8 @@ fileHandler { } updater { - releasesUrl = "https://github.com/jendakol/rbackup-scala-client/releases" - checkPeriod = 1 minute + releasesUrl = "https://api.github.com/repos/jendakol/rbackup-scala-client/releases" + checkPeriod = 10 minutes } sentry {