diff --git a/build.mill b/build.mill index 3301848..de4b8fd 100644 --- a/build.mill +++ b/build.mill @@ -1,17 +1,30 @@ //| mill-version: 1.1.8 //| mill-jvm-version: temurin:21.0.6 -import mill.*, scalalib.*, publish.* +import mill.*, scalalib.*, scalajslib.*, publish.* -object protocol extends CommonSettings { - def mvnDeps = Seq( - Deps.bittorrent.common, - Deps.upickle, - Deps.`scodec-bits`, - ) +object protocol extends Module { + object jvm extends CommonSettings, PlatformScalaModule { + override def scalaVersion = "3.3.6" + def mvnDeps = Seq( + Deps.upickle, + Deps.`scodec-bits`, + ) + } + object js extends CommonSettings, ScalaJSModule, PlatformScalaModule { + override def scalaVersion = "3.3.6" + def scalaJSVersion = "1.20.1" + def mvnDeps = Seq( + Deps.upickle, + Deps.`scodec-bits`, + ) + } } object server extends CommonSettings { - def moduleDeps = Seq(protocol) + def moduleDeps = Seq(protocol.jvm) + def scalacOptions = Seq( + "-Xkind-projector:underscores", + ) def mvnDeps = Seq( Deps.bittorrent.bittorrent, Deps.bittorrent.dht, @@ -31,7 +44,6 @@ trait CommonSettings extends ScalaModule, PublishModule { def scalaVersion = "3.8.3" def scalacOptions = Seq( "-source:future", - "-Xkind-projector:underscores", ) def sources = Task.Sources( moduleDir / "src" / "main" / "scala" diff --git a/protocol/src/main/scala/com/github/lavrov/bittorrent/app/protocol.scala b/protocol/src/main/scala/com/github/lavrov/bittorrent/app/protocol.scala index a29cd95..5fd7026 100644 --- a/protocol/src/main/scala/com/github/lavrov/bittorrent/app/protocol.scala +++ b/protocol/src/main/scala/com/github/lavrov/bittorrent/app/protocol.scala @@ -1,9 +1,20 @@ package com.github.lavrov.bittorrent.app.protocol -import com.github.torrentdam.bittorrent.InfoHash import scodec.bits.ByteVector import upickle.default.{macroRW, ReadWriter} +final case class InfoHash(bytes: ByteVector) { + override def toString: String = bytes.toHex +} + +object InfoHash { + + def fromString(source: String): Option[InfoHash] = + ByteVector.fromHex(source) + .filter(_.length == 20) + .map(InfoHash.apply) +} + sealed trait Message sealed trait Command extends Message sealed trait Event extends Message @@ -40,4 +51,4 @@ object Message { macroRW[TorrentError], macroRW[TorrentStats], ) -} \ No newline at end of file +} diff --git a/server/src/main/scala/SocketSession.scala b/server/src/main/scala/SocketSession.scala index 8a2e56b..3414d12 100644 --- a/server/src/main/scala/SocketSession.scala +++ b/server/src/main/scala/SocketSession.scala @@ -4,6 +4,7 @@ import cats.effect.std.Queue import cats.effect.std.Supervisor import cats.implicits.* import com.github.torrentdam.bittorrent.InfoHash +import com.github.lavrov.bittorrent.app.protocol import com.github.lavrov.bittorrent.app.protocol.{Message, Event, Command} import fs2.Stream import org.legogroup.woof.{Logger, given} @@ -23,7 +24,7 @@ object SocketSession { makeTorrent: MakeTorrent, metadataRegistry: MetadataRegistry[IO], torrentIndex: TorrentIndex, - webSocketBuilder: WebSocketBuilder[IO] + webSocketBuilder: WebSocketBuilder[IO] )(using F: Concurrent[IO], logger: Logger[IO] @@ -86,6 +87,7 @@ object SocketSession { yield () private def handleRequestTorrent(infoHash: InfoHash, trackers: List[String]): IO[Unit] = + val messageInfoHash = protocol.InfoHash(infoHash.bytes) supervisor.supervise( makeTorrent(infoHash, trackers) .use { getTorrent => @@ -96,7 +98,7 @@ object SocketSession { .flatMap { (phase: ServerTorrent.Phase.FetchingMetadata) => phase.fromPeers.discrete .evalTap { count => - send(Message.TorrentPeersDiscovered(infoHash, count)) + send(Message.TorrentPeersDiscovered(messageInfoHash, count)) } .interruptWhen(phase.done.void.attempt) .compile @@ -106,20 +108,20 @@ object SocketSession { .flatMap { (phase: ServerTorrent.Phase.Ready) => val metadata = phase.serverTorrent.metadata.parsed val files = metadata.files.map(f => Message.File(f.path, f.length)) - send(Message.TorrentMetadataReceived(infoHash, metadata.name, files)) >> + send(Message.TorrentMetadataReceived(messageInfoHash, metadata.name, files)) >> phase.serverTorrent.pure[IO] } .timeout(5.minutes) .flatMap { torrent => - sendTorrentStats(infoHash, torrent) + sendTorrentStats(messageInfoHash, torrent) } } .orElse { - send(Message.TorrentError(infoHash, "Could not fetch metadata")) + send(Message.TorrentError(messageInfoHash, "Could not fetch metadata")) } ).void - private def sendTorrentStats(infoHash: InfoHash, torrent: ServerTorrent): IO[Nothing] = + private def sendTorrentStats(infoHash: protocol.InfoHash, torrent: ServerTorrent): IO[Nothing] = val sendStats = for stats <- torrent.stats @@ -150,7 +152,7 @@ object SocketSession { supervisor ) } - + trait MakeTorrent { def apply(infoHash: InfoHash, trackers: List[String]): Resource[IO, IO[ServerTorrent.Phase.PeerDiscovery]] }