|
|
package wow.doge.mygame.subsystems.moddingsystem import java.io.FileNotFoundException import java.nio.file.NoSuchFileException
import scala.collection.View import scala.collection.immutable.ArraySeq
import cats.Show import cats.implicits._ import cats.kernel.Eq import io.circe._ import io.circe.generic.JsonCodec import io.circe.generic.semiauto._ import io.circe.parser._ import monix.bio.IO import monix.bio.UIO import monix.reactive.Consumer import monix.reactive.Observable import wow.doge.mygame.utils.IOUtils
import IOUtils.toIO
@JsonCodec final case class Test1(hello1: String, hello2: String) @JsonCodec final case class Test2(hello1: String) final case class Plugin(name: String, priority: Int) object Plugin { implicit val decoder: Decoder[Plugin] = deriveDecoder implicit val show = Show.fromToString[Plugin] implicit val eq = Eq.fromUniversalEquals[Plugin] }
object ModdingSystem { sealed trait Error final case class CouldNotDecode(cause: String) extends Error final case class ParseFailure(cause: io.circe.ParsingFailure) extends Error final case class DecodingFailure(cause: io.circe.DecodingFailure) extends Error final case class FileNotFound(path: os.Path) extends Error object Error { implicit val show = Show.fromToString[Error] implicit val eq = Eq.fromUniversalEquals[Error] }
def readPluginsList(dir: os.Path): IO[Error, ArraySeq[Plugin]] = IO(parse(os.read(dir / "plugins.json"))) .onErrorHandleWith { case _: FileNotFoundException => IO.raiseError(FileNotFound(dir / "plugins.json")) case _: NoSuchFileException => IO.raiseError(FileNotFound(dir / "plugins.json")) } .flatMap(files => IO.fromEither(files) .map(_.as[ArraySeq[Plugin]]) .mapError(ParseFailure) ) .flatMap(result => IO.fromEither(result).mapError(DecodingFailure))
def findPluginFiles(dir: os.Path): View[os.Path] = os.list(dir) .view .filter(f => f.ext == "json" && f.baseName.endsWith("plugin"))
def findAndReadPluginFiles( dir: os.Path, plugins: ArraySeq[Plugin] ): UIO[(View[(Plugin, Error)], View[(Plugin, String)])] = UIO( plugins .sortBy(_.priority) .view .map { p => val path = dir / os.RelPath(p.name + ".plugin.json") p -> Either .catchNonFatal(os.read(path)) .leftMap { case _: FileNotFoundException => FileNotFound(path) case _: NoSuchFileException => FileNotFound(path) }
} .partitionMap { case (p, either) => either match { case Left(value) => Left(p -> value) case Right(value) => Right(p -> value) } } ) // : (View[(Plugin, Error)], View[(Plugin, String)])
def findAndReadPluginFiles2( dir: os.Path, plugins: ArraySeq[Plugin] ) = // IO.parTraverse(plugins.sortBy(_.priority))(p =>
// IO {
// val path = dir / os.RelPath(p.name + ".plugin.json")
// os.read(path)
// }
// .onErrorHandleWith {
// case _: FileNotFoundException =>
// IO.raiseError(FileNotFound(dir.toString))
// case _: NoSuchFileException =>
// IO.raiseError(FileNotFound(dir.toString))
// }
// .flatMap(r => UIO(p -> r))
// ).map {
// _.partitionMap {
// case (p, either) =>
// either match {
// case Left(value) => Left(p -> value)
// case Right(value) => Right(p -> value)
// }
// }
// }
plugins .sortBy(_.priority) .view .map { p => val path = dir / os.RelPath(p.name + ".plugin.json") p -> IO(os.read(path)) .onErrorHandleWith { case _: FileNotFoundException => IO.raiseError(FileNotFound(path)) case _: NoSuchFileException => IO.raiseError(FileNotFound(path)) } // .map(r => p -> r)
} .map { case (p, io) => io.attempt.map { case Left(value) => Left(p -> value) case Right(value) => Right(p -> value) } } .to(List) .parSequence
// .partitionMap {
// _.map {
// case l @ Left(value) => l
// case r @ Right(value) => r
// }
// }
// .sequence
// def readPluginFiles(filePaths: View[os.Path]) =
// filePaths.map(path => os.read(path))
// def readPluginFiles2(filePaths: View[os.Path]) =
// filePaths
// .map(path =>
// IO(os.read(path)).onErrorHandleWith {
// case _: FileNotFoundException =>
// IO.raiseError(FileNotFound(path))
// case _: NoSuchFileException =>
// IO.raiseError(FileNotFound(path))
// }
// )
// .to(List)
// .parSequence
def parsePluginFiles(files: View[(Plugin, String)]) = files .map { case (p, s) => p -> parse(s) } .partitionMap { case (p, Left(value)) => Left(p -> value) case (p, Right(value)) => Right(p -> value) }
val emptyJson = Json.fromString("empty")
val foldFn: (Json, Json) => Json = { case (json, Json.Null) => json //ignore null values
case (json, value) => json.deepMerge(value) }
def mergePluginDataConsumer = Consumer.foldLeft[Json, Json](emptyJson)(foldFn)
def loadBalancedPluginDataMerger = Consumer .loadBalance(parallelism = 2, mergePluginDataConsumer) .map(_.foldLeft(emptyJson)(foldFn))
def run(wd: os.Path = os.pwd) = for { plugins <- readPluginsList(wd) (readFailures, readSuccesses) <- findAndReadPluginFiles(wd, plugins) (parseFailures, parseSuccesses) <- UIO(parsePluginFiles(readSuccesses)) res <- UIO.parMap5( UIO(readFailures.to(List)), UIO(readSuccesses.to(List)), UIO(parseFailures.to(List)), UIO(parseSuccesses.to(List)), toIO( Observable .fromIterable(parseSuccesses) .map { case (p, json) => json } .consumeWith(loadBalancedPluginDataMerger) ).hideErrors )(Result.apply) } yield res
def log(res: Result) = UIO { pprint.log(show"Read Successes = ${res.readSuccesses}") pprint.log(show"Read Failures = ${res.readFailures}") pprint.log(show"Parse Successes = ${res.parseSuccesses}") pprint.log(show"Parse Failures = ${res.parseFailures}") pprint.log(show"Merged = ${res.pluginJson}") }
case class Result( readFailures: List[(Plugin, Error)], readSuccesses: List[(Plugin, String)], parseFailures: List[(Plugin, ParsingFailure)], parseSuccesses: List[(Plugin, Json)], pluginJson: Json ) object Result { implicit val show = Show.fromToString[Result] // implicit val eq = Eq.fromUniversalEquals[Error]
}
}
|