Skip to content

Instantly share code, notes, and snippets.

@gekomad
Created November 5, 2024 14:34
Show Gist options
  • Save gekomad/e1f04b4c64f4e223b7fc59d7ec8862fb to your computer and use it in GitHub Desktop.
Save gekomad/e1f04b4c64f4e223b7fc59d7ec8862fb to your computer and use it in GitHub Desktop.
FTP scala and cats
import cats.effect.IO
import cats.effect.Resource.fromAutoCloseable
import org.apache.commons.net.ftp.{FTP, FTPClient}
import org.slf4j.{Logger, LoggerFactory}
import cats.implicits._
import java.io._
import scala.util.Try
/* use:
libraryDependencies += "commons-net" % "commons-net" % "3.10.0"
val remoteFeedbackFolder = ???
val localFeedbackFolder = ???
ftpClient(
host = ???,
username = ???,
password = ???
)(ftpClient => {
ftpClient.getAllFiles(remoteFeedbackFolder, localFeedbackFolder)
})
*/
object ftpClient {
private val log: Logger = LoggerFactory.getLogger(this.getClass)
def apply[A](host: String, username: String, password: String)(f: ftpClient => IO[A]): IO[A] = {
val c: ftpClient = ftpClient(host: String, username: String, password: String)
for {
a <- f(c)
_ <- c.disconnect()
} yield a
}
private[ftpClient] final case class ftpClient(host: String, username: String, password: String) {
def rename(from: String, to: String): IO[Boolean] = client.map { client =>
log.debug(s"rename $from $to")
client.rename(from, to)
}
private val client: IO[FTPClient] = connect(host, username, password)
private def connect(host: String, username: String, password: String): IO[FTPClient] =
IO.fromTry(Try {
val client = new FTPClient()
log.info(s"Connecting to $host..")
client.connect(host.toString)
log.info(s"login $username..")
client.login(username, password)
client.enterLocalPassiveMode()
client.setFileType(FTP.BINARY_FILE_TYPE)
client
})
def disconnect(): IO[Unit] =
client.map { client =>
log.debug("disconnect")
scala.util.Try(client.logout())
scala.util.Try(client.disconnect())
()
}
private def getFile(remoteFilePath: String, localDir: String): IO[Option[String]] = {
val localName = Try {
val localName = s"$localDir/${FileUtil.getFileName(remoteFilePath)}"
log.debug(s"remoteFile: $remoteFilePath")
log.debug(s"localName: $localName")
val del = FileUtil.deleteFile(localName)
val get = fromAutoCloseable(IO(new FileOutputStream(localName)))
.use(output =>
client.map { a =>
val b = a.retrieveFile(remoteFilePath, output)
if (b) Some(localName) else None
}
)
del *> get
}
IO.fromTry(localName).flatMap(identity)
}
def getAllFiles(remoteDir: String, localDir: String): IO[List[String]] = {
log.debug(s"getAllFiles $remoteDir...")
client.flatMap { c =>
val files = c.listFiles(remoteDir).toList
log.debug(s"getAllFiles - file and folder count: ${files.size}")
files.foreach(a => log.debug(s"getAllFiles name ${a.getName}"))
val filesName = files.map { a =>
if (a.isFile) getFile(remoteFilePath = s"$remoteDir/${a.getName}", localDir = localDir)
else IO(None)
}.sequence
filesName.map(_.flatten)
}
}
def mkdir(remoteDir: String): IO[Int] = {
log.info(s"Creating directory $remoteDir...")
client.map(_.mkd(remoteDir))
}
def send(fileName: String, optDir: Option[String], remoteFileName: Option[String]): IO[String] = {
val remoteFileName2 = remoteFileName match {
case Some(value) => value
case None => FileUtil.getFileName(fileName)
}
for {
_ <- optDir.fold(IO.unit) { remoteDir =>
log.info(s"Changing working directory to $remoteDir...")
client.map(_.changeWorkingDirectory(remoteDir)).flatMap { changed =>
if (!changed) IO.raiseError(new Exception(s"error on changing remote dir $remoteDir"))
else {
log.info("working directory changed")
IO.unit
}
}
}
theFileName <- fromAutoCloseable(IO(new FileInputStream(new File(fileName)))) use { fis =>
client.map { c =>
c.storeFile(remoteFileName2, fis)
val replyString = c.getReplyString
log.info(s"Upload $fileName done. Reply is: " + replyString)
fileName
}
}
} yield theFileName
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment