diff --git a/build.sbt b/build.sbt index 6ffb5c1..adc010a 100644 --- a/build.sbt +++ b/build.sbt @@ -29,7 +29,8 @@ libraryDependencies ++= Seq( "ch.qos.logback" % "logback-core" % logbackVersion, "com.typesafe.akka" %% "akka-testkit" % akkaVersion % Test, "org.scalatest" %% "scalatest" % "3.0.5" % Test, - "com.typesafe.akka" %% "akka-http-testkit" % akkaHttpVersion % Test + "com.typesafe.akka" %% "akka-http-testkit" % akkaHttpVersion % Test, + "com.github.pathikrit" %% "better-files" % "3.6.0" ) resolvers ++= Seq("Sonatype Releases" at "https://oss.sonatype.org/content/repositories/releases/", diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 922be25..c2ac70f 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -11,6 +11,7 @@ httpHost = "0.0.0.0" httpPort = 9151 timeout = 10000 + enableStateDownload = false } influx { host = "http://127.0.0.1:8086" diff --git a/src/main/scala/mvp2/actors/Downloader.scala b/src/main/scala/mvp2/actors/Downloader.scala new file mode 100644 index 0000000..3c4ccd5 --- /dev/null +++ b/src/main/scala/mvp2/actors/Downloader.scala @@ -0,0 +1,60 @@ +package mvp2.actors + +import java.nio.file.Paths +import akka.pattern.pipe +import akka.http.scaladsl.Http +import akka.http.scaladsl.model.{HttpRequest, HttpResponse, StatusCodes} +import akka.stream.scaladsl.FileIO +import better.files.File +import better.files._ +import scala.util.{Failure, Success} +import mvp2.MVP2._ +import scala.concurrent.ExecutionContext.Implicits.global + +class Downloader(downloadFrom: String) extends CommonActor { + override def specialBehavior: Receive = ??? + + override def preStart(): Unit = { + logger.info("Starting downloader") + self ! downloadFrom + } + + override def receive: Receive = { + case address: String => + pipe(Http(context.system).singleRequest(HttpRequest(uri = s"http://$address/download"))).to(self) + case HttpResponse(StatusCodes.OK, _, entity, _) => + entity.dataBytes.runWith(FileIO.toPath(Paths.get(s"./state.zip"))).onComplete { + case Success(_) => + unzip() + println(1) + context.parent ! DownloadComplete + case Failure(th) => + th.printStackTrace() + context.parent ! DownloadComplete + context.stop(self) + } + case resp @ HttpResponse(code, _, _, _) => + println(code) + resp.discardEntityBytes() + context.parent ! DownloadComplete + context.stop(self) + } + + def unzip(): Unit = { + def moveAllFiles(from: File, dest: File): Unit = from.list.foreach(_.copyToDirectory(dest)) + val zip: File = file"./encry.zip" + val unzipped: File = zip.unzipTo(file"./state") + zip.delete(true) + file"./leveldb/journal".delete(true) + file"./leveldb/snapshots".delete(true) + val unzippedJournal: File = file"./state/journal" + val unzippedSnapshots: File = file"./state/snapshots" + val journalDir: File = file"./leveldb/journal".createDirectories() + val snapshotsDir: File = file"./leveldb/snapshots".createDirectories() + moveAllFiles(unzippedJournal, journalDir) + moveAllFiles(unzippedSnapshots, snapshotsDir) + unzipped.delete(true) + } +} + +case object DownloadComplete diff --git a/src/main/scala/mvp2/actors/Starter.scala b/src/main/scala/mvp2/actors/Starter.scala index 87d9db8..1b0a5cc 100644 --- a/src/main/scala/mvp2/actors/Starter.scala +++ b/src/main/scala/mvp2/actors/Starter.scala @@ -14,11 +14,15 @@ class Starter extends CommonActor { override def preStart(): Unit = { logger.info("Starting the Starter!") - bornKids() + settings.downloadStateFrom match { + case None => bornKids() + case Some(address) => context.actorOf(Props(classOf[Downloader], address), "downloader") + } } override def specialBehavior: Receive = { case message: String => logger.info(message) + case DownloadComplete if settings.downloadStateFrom.nonEmpty => bornKids() } def bornKids(): Unit = { diff --git a/src/main/scala/mvp2/http/Routes.scala b/src/main/scala/mvp2/http/Routes.scala index af5f141..69b6d58 100644 --- a/src/main/scala/mvp2/http/Routes.scala +++ b/src/main/scala/mvp2/http/Routes.scala @@ -1,25 +1,29 @@ package mvp2.http +import java.nio.file.Path import akka.http.scaladsl.server.Directives.complete import akka.actor.ActorSelection -import akka.http.scaladsl.model.{ContentTypes, HttpEntity} +import akka.http.scaladsl.model._ import mvp2.data.{LightKeyBlock, Transaction} import mvp2.utils.Settings import akka.actor.ActorRefFactory -import akka.http.scaladsl.model.StatusCodes +import akka.http.scaladsl.model.StatusCodes.InternalServerError import akka.http.scaladsl.server.Route import io.circe.Json -import scala.concurrent.Future import io.circe.generic.auto._ import io.circe.syntax._ import scala.concurrent.ExecutionContextExecutor import scala.concurrent.duration._ +import scala.concurrent.Future import scala.language.postfixOps import akka.http.scaladsl.server.Directives._ import akka.pattern.ask import akka.util.{ByteString, Timeout} +import better.files.File +import better.files._ import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport import mvp2.data.InnerMessages.{CurrentBlockchainInfo, Get, GetLightChain} +import scala.util.{Failure, Success, Try} import mvp2.utils.EncodingUtils._ case class Routes(settings: Settings, implicit val context: ActorRefFactory) extends FailFastCirceSupport { @@ -29,7 +33,11 @@ case class Routes(settings: Settings, implicit val context: ActorRefFactory) ext implicit val timeout: Timeout = Timeout(settings.apiSettings.timeout.millisecond) implicit val ec: ExecutionContextExecutor = context.dispatcher - val route: Route = getTxs ~ apiInfo ~ chainInfo + val routes: Seq[Route] = + if (settings.apiSettings.enableStateDownload) Seq(getTxs, apiInfo, chainInfo, downloadState) + else Seq(getTxs, apiInfo, chainInfo) + + val route: Route = routes.reduce(_ ~ _) val publisher: ActorSelection = context.actorSelection("/user/starter/blockchainer/publisher") val informator: ActorSelection = context.actorSelection("/user/starter/informator") @@ -57,4 +65,22 @@ case class Routes(settings: Settings, implicit val context: ActorRefFactory) ext def chainInfo: Route = path("chainInfo")( toJsonResponse((informator ? GetLightChain).mapTo[List[LightKeyBlock]].map(_.asJson)) ) + + def downloadState: Route = (path("download") & get)( + createZip match { + case Success(path) => getFromFile(path.toFile, MediaTypes.`application/zip`) + case Failure(_) => complete(HttpResponse(InternalServerError)) + } + ) + + private def createZip: Try[Path] = Try { + file"./state.zip".delete(true) + file"./tmp/".delete(true) + val dir: File = file"./tmp/".createDirectory() + File("./leveldb").list.map(_.copyToDirectory(dir)) + val zip: File = file"./state.zip".deleteOnExit() + dir.zipTo(file"./state.zip").deleteOnExit() + zip.path + } + } \ No newline at end of file diff --git a/src/main/scala/mvp2/utils/Settings.scala b/src/main/scala/mvp2/utils/Settings.scala index 9b62fe8..ed62a55 100644 --- a/src/main/scala/mvp2/utils/Settings.scala +++ b/src/main/scala/mvp2/utils/Settings.scala @@ -7,6 +7,7 @@ case class Settings(port: Int, blockPeriod: Long, biasForBlockPeriod: Long, newBlockchain: Boolean, + downloadStateFrom: Option[String] = None, apiSettings: ApiSettings, ntp: NetworkTimeProviderSettings, influx: InfluxSettings, @@ -16,7 +17,7 @@ case class Settings(port: Int, case class Node(host: String, port: Int) -case class ApiSettings(httpHost: String, httpPort: Int, timeout: Int) +case class ApiSettings(httpHost: String, httpPort: Int, timeout: Int, enableStateDownload: Boolean) case class InfluxSettings(host: String, port: Int, login: String, password: String)