@@ -2,8 +2,8 @@ package ray.fs2.ftp
22
33import java .io ._
44
5- import cats .effect .{ Blocker , ContextShift , IO , Resource }
6- import cats .syntax . applicativeError ._
5+ import cats .effect .{ Blocker , ConcurrentEffect , ContextShift , Resource }
6+ import cats .implicits ._
77import fs2 .Stream
88import fs2 .Stream ._
99import net .schmizz .sshj .SSHClient
@@ -14,33 +14,36 @@ import ray.fs2.ftp.FtpSettings.{ KeyFileSftpIdentity, RawKeySftpIdentity, Secure
1414
1515import scala .jdk .CollectionConverters ._
1616
17- final private class SFtp (unsafeClient : JSFTPClient , blocker : Blocker ) extends FtpClient [JSFTPClient ] {
17+ final private class SFtp [F [_]](unsafeClient : JSFTPClient , blocker : Blocker )(
18+ implicit CE : ConcurrentEffect [F ],
19+ CS : ContextShift [F ]
20+ ) extends FtpClient [F , JSFTPClient ] {
1821
19- def ls (path : String )( implicit cs : ContextShift [ IO ]) : fs2.Stream [IO , FtpResource ] =
22+ def ls (path : String ): fs2.Stream [F , FtpResource ] =
2023 fs2.Stream
2124 .evalSeq(execute(_.ls(path).asScala.toSeq))
2225 .map(FtpResource (_))
2326 .recoverWith {
24- case ex : SFTPException if ex.getStatusCode == Response .StatusCode .NO_SUCH_FILE => fs2.Stream .empty.covary[IO ]
25- case other => fs2.Stream .raiseError[IO ](other)
27+ case ex : SFTPException if ex.getStatusCode == Response .StatusCode .NO_SUCH_FILE => fs2.Stream .empty.covary[F ]
28+ case other => fs2.Stream .raiseError[F ](other)
2629 }
2730
28- def lsDescendant (path : String )( implicit cs : ContextShift [ IO ]) : fs2.Stream [IO , FtpResource ] =
31+ def lsDescendant (path : String ): fs2.Stream [F , FtpResource ] =
2932 fs2.Stream
3033 .evalSeq(execute(_.ls(path).asScala.toSeq))
3134 .flatMap(f => if (f.isDirectory) lsDescendant(f.getPath) else Stream (FtpResource (f)))
3235 .recoverWith {
33- case ex : SFTPException if ex.getStatusCode == Response .StatusCode .NO_SUCH_FILE => fs2.Stream .empty.covary[IO ]
34- case other => fs2.Stream .raiseError[IO ](other)
36+ case ex : SFTPException if ex.getStatusCode == Response .StatusCode .NO_SUCH_FILE => fs2.Stream .empty.covary[F ]
37+ case other => fs2.Stream .raiseError[F ](other)
3538 }
3639
37- def stat (path : String )( implicit cs : ContextShift [ IO ]) : IO [Option [FtpResource ]] =
40+ def stat (path : String ): F [Option [FtpResource ]] =
3841 execute(client => Option (client.statExistence(path)).map(FtpResource (path, _)))
3942
4043 def readFile (
4144 path : String ,
4245 chunkSize : Int = 10 * 1024
43- )( implicit cs : ContextShift [ IO ]) : fs2.Stream [IO , Byte ] =
46+ ): fs2.Stream [F , Byte ] =
4447 for {
4548 remoteFile <- Stream .eval(execute(_.open(path, java.util.EnumSet .of(OpenMode .READ ))))
4649
@@ -54,22 +57,22 @@ final private class SFtp(unsafeClient: JSFTPClient, blocker: Blocker) extends Ft
5457 }
5558 }
5659
57- input <- fs2.io.readInputStream(IO .pure(is), chunkSize, blocker)
60+ input <- fs2.io.readInputStream(CE .pure(is), chunkSize, blocker)
5861 } yield input
5962
60- def rm (path : String )( implicit cs : ContextShift [ IO ]) : IO [Unit ] =
63+ def rm (path : String ): F [Unit ] =
6164 execute(_.rm(path))
6265
63- def rmdir (path : String )( implicit cs : ContextShift [ IO ]) : IO [Unit ] =
66+ def rmdir (path : String ): F [Unit ] =
6467 execute(_.rmdir(path))
6568
66- def mkdir (path : String )( implicit cs : ContextShift [ IO ]) : IO [Unit ] =
69+ def mkdir (path : String ): F [Unit ] =
6770 execute(_.mkdir(path))
6871
6972 def upload (
7073 path : String ,
71- source : fs2.Stream [IO , Byte ]
72- )( implicit cs : ContextShift [ IO ]) : IO [Unit ] =
74+ source : fs2.Stream [F , Byte ]
75+ ): F [Unit ] =
7376 (for {
7477 remoteFile <- Stream .eval(execute(_.open(path, java.util.EnumSet .of(OpenMode .WRITE , OpenMode .CREAT ))))
7578
@@ -82,21 +85,23 @@ final private class SFtp(unsafeClient: JSFTPClient, blocker: Blocker) extends Ft
8285 super .close()
8386 }
8487 }
85- _ <- source.through(fs2.io.writeOutputStream(IO .pure(os), blocker))
88+ _ <- source.through(fs2.io.writeOutputStream(CE .pure(os), blocker))
8689 } yield ()).compile.drain
8790
88- def execute [T ](f : JSFTPClient => T )( implicit cs : ContextShift [ IO ]) : IO [T ] =
89- blocker.delay[IO , T ](f(unsafeClient))
91+ def execute [T ](f : JSFTPClient => T ): F [T ] =
92+ blocker.delay[F , T ](f(unsafeClient))
9093}
9194
9295object SFtp {
9396
94- def connect (settings : SecureFtpSettings )(implicit cs : ContextShift [IO ]): Resource [IO , FtpClient [JSFTPClient ]] =
97+ def connect [F [_]](
98+ settings : SecureFtpSettings
99+ )(implicit CS : ContextShift [F ], CE : ConcurrentEffect [F ]): Resource [F , FtpClient [F , JSFTPClient ]] =
95100 for {
96- ssh <- Resource .liftF(IO (new SSHClient (settings.sshConfig)))
101+ ssh <- Resource .liftF(CE .delay (new SSHClient (settings.sshConfig)))
97102
98- blocker <- Blocker [IO ]
99- r <- Resource .make[IO , FtpClient [JSFTPClient ]](IO .delay {
103+ blocker <- Blocker [F ]
104+ r <- Resource .make[F , FtpClient [F , JSFTPClient ]](CE .delay {
100105 import settings ._
101106
102107 if (! strictHostKeyChecking)
@@ -116,7 +121,7 @@ object SFtp {
116121
117122 new SFtp (ssh.newSFTPClient(), blocker)
118123 })(client =>
119- client.execute(_.close()).attempt.flatMap(_ => if (ssh.isConnected) IO .delay(ssh.disconnect()) else IO .unit)
124+ client.execute(_.close()).attempt.flatMap(_ => if (ssh.isConnected) CE .delay(ssh.disconnect()) else CE .unit)
120125 )
121126 } yield r
122127
0 commit comments