Skip to content

Commit

Permalink
FTP - attribute enrichment of FTPFile akka#153
Browse files Browse the repository at this point in the history
  • Loading branch information
svezfaz authored and sbonettiEXPE committed Feb 20, 2017
1 parent a7872e0 commit 0a02acd
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
package akka.stream.alpakka.ftp
package impl

import org.apache.commons.net.ftp.{FTP, FTPClient}
import org.apache.commons.net.ftp.{FTP, FTPClient, FTPFile}
import scala.collection.immutable
import scala.util.Try
import java.io.{IOException, InputStream}
import java.nio.file.Paths
import java.nio.file.attribute.PosixFilePermission

private[ftp] trait FtpOperations { _: FtpLike[FTPClient, FtpFileSettings] =>

Expand Down Expand Up @@ -40,11 +41,33 @@ private[ftp] trait FtpOperations { _: FtpLike[FTPClient, FtpFileSettings] =>
handler
.listFiles(path)
.map { file =>
FtpFile(file.getName, Paths.get(s"$path/${file.getName}").normalize.toString, file.isDirectory)
FtpFile(
file.getName,
Paths.get(s"$path/${file.getName}").normalize.toString,
file.isDirectory,
file.getSize,
file.getTimestamp.getTimeInMillis,
getPosixFilePermissions(file)
)
}
.toVector
}

private def getPosixFilePermissions(file: FTPFile) =
Map(
PosixFilePermission.OWNER_READ file.hasPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION),
PosixFilePermission.OWNER_WRITE file.hasPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION),
PosixFilePermission.OWNER_EXECUTE file.hasPermission(FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION),
PosixFilePermission.GROUP_READ file.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.READ_PERMISSION),
PosixFilePermission.GROUP_WRITE file.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.WRITE_PERMISSION),
PosixFilePermission.GROUP_EXECUTE file.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.EXECUTE_PERMISSION),
PosixFilePermission.OTHERS_READ file.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.READ_PERMISSION),
PosixFilePermission.OTHERS_WRITE file.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.WRITE_PERMISSION),
PosixFilePermission.OTHERS_EXECUTE file.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.EXECUTE_PERMISSION)
).collect {
case (perm, true) perm
}.toSet

def listFiles(handler: Handler): immutable.Seq[FtpFile] = listFiles("", handler)

def retrieveFileInputStream(name: String, handler: Handler): Try[InputStream] = Try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import akka.stream.alpakka.ftp.RemoteFileSettings.SftpSettings
import com.jcraft.jsch.{ChannelSftp, JSch}
import scala.collection.immutable
import scala.util.Try
import scala.collection.JavaConverters._
import java.io.InputStream
import java.nio.file.Paths
import java.nio.file.attribute.PosixFilePermissions

private[ftp] trait SftpOperations { _: FtpLike[JSch, SftpSettings] =>

Expand Down Expand Up @@ -49,10 +51,25 @@ private[ftp] trait SftpOperations { _: FtpLike[JSch, SftpSettings] =>
} // TODO
entries.map {
case entry: Handler#LsEntry =>
FtpFile(entry.getFilename, Paths.get(s"$path/${entry.getFilename}").normalize.toString, entry.getAttrs.isDir)
FtpFile(
entry.getFilename,
Paths.get(s"$path/${entry.getFilename}").normalize.toString,
entry.getAttrs.isDir,
entry.getAttrs.getSize,
entry.getAttrs.getMTime * 1000L,
getPosixFilePermissions(entry.getAttrs.getPermissionsString)
)
}.toVector
}

private def getPosixFilePermissions(permissions: String) =
PosixFilePermissions
.fromString(
permissions.replace('s', '-').drop(1)
)
.asScala
.toSet

def listFiles(handler: Handler): immutable.Seq[FtpFile] = listFiles(".", handler) // TODO

def retrieveFileInputStream(name: String, handler: Handler): Try[InputStream] = Try {
Expand Down
10 changes: 9 additions & 1 deletion ftp/src/main/scala/akka/stream/alpakka/ftp/model.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
package akka.stream.alpakka.ftp

import akka.stream.alpakka.ftp.FtpCredentials.AnonFtpCredentials

import scala.language.implicitConversions
import java.net.InetAddress
import java.nio.file.attribute.PosixFilePermission

/**
* FTP remote file descriptor.
Expand All @@ -14,11 +16,17 @@ import java.net.InetAddress
* @param path remote file path as viewed by the logged user.
* It should always start by '/'
* @param isDirectory the descriptor is a directory
* @param size the file size in bytes
* @param lastModified the timestamp of the file last modification
* @param permissions the permissions of the file
*/
final case class FtpFile(
name: String,
path: String,
isDirectory: Boolean
isDirectory: Boolean,
size: Long,
lastModified: Long,
permissions: Set[PosixFilePermission]
) {
val isFile: Boolean = !this.isDirectory
}
Expand Down
2 changes: 2 additions & 0 deletions ftp/src/test/java/akka/stream/alpakka/ftp/FtpBaseSupport.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
import akka.stream.ActorMaterializer;
import akka.stream.Materializer;
import org.apache.mina.util.AvailablePortFinder;

import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.PosixFilePermissions;

abstract class FtpBaseSupport implements FtpSupport, AkkaSupport {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ abstract class PlainFtpSupportImpl extends FtpSupportImpl {
static final String DEFAULT_LISTENER = "default";

protected FtpServerFactory createFtpServerFactory(Integer port) {
setFileSystem(Jimfs.newFileSystem(Configuration.unix()));
Configuration fsConfig = Configuration.unix().toBuilder().setAttributeViews("basic", "posix").build();
setFileSystem(Jimfs.newFileSystem(fsConfig));
JimfsFactory fsf = new JimfsFactory(getFileSystem());
fsf.setCreateHome(true);

Expand Down
2 changes: 1 addition & 1 deletion ftp/src/test/resources/users.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
ftpserver.user.anonymous
ftpserver.user.anonymous.homedirectory=/home
ftpserver.user.anonymous.writepermission=false
ftpserver.user.anonymous.writepermission=true
4 changes: 3 additions & 1 deletion ftp/src/test/scala/akka/stream/alpakka/ftp/BaseSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import akka.NotUsed
import akka.stream.IOResult
import akka.stream.scaladsl.Source
import akka.util.ByteString
import org.scalatest.{BeforeAndAfter, BeforeAndAfterAll, Inside, Matchers, WordSpecLike}
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.{BeforeAndAfter, BeforeAndAfterAll, Matchers, WordSpecLike}

import scala.concurrent.{Await, Future}
import scala.concurrent.duration.DurationInt

Expand All @@ -18,6 +19,7 @@ trait BaseSpec
with BeforeAndAfter
with BeforeAndAfterAll
with ScalaFutures
with Inside
with AkkaSupport
with FtpSupport {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import akka.stream.IOResult
import akka.stream.scaladsl.{Keep, Sink}
import akka.stream.testkit.scaladsl.TestSink
import org.scalatest.time.{Millis, Seconds, Span}
import scala.concurrent.duration._
import scala.util.Random
import java.nio.file.attribute.PosixFilePermission

final class FtpSourceSpec extends BaseFtpSpec with CommonFtpSourceSpec
final class SftpSourceSpec extends BaseSftpSpec with CommonFtpSourceSpec
Expand All @@ -32,6 +34,7 @@ trait CommonFtpSourceSpec extends BaseSpec {
probe.request(40).expectNextN(30)
probe.expectComplete()
}

"list all files from non-root" in {
val basePath = "/foo"
generateFiles(30, 10, basePath)
Expand All @@ -50,6 +53,26 @@ trait CommonFtpSourceSpec extends BaseSpec {
probe.request(2).expectNextN(1)
probe.expectComplete()
}

"retrieve relevant file attributes" in {
val fileName = "sample"
val basePath = "/"

putFileOnFtp(FtpBaseSupport.FTP_ROOT_DIR, fileName)

val files = listFiles(basePath).runWith(Sink.seq).futureValue

files should have size 1
inside(files.head) {
case FtpFile(actualFileName, actualPath, isDirectory, size, lastModified, perms)
actualFileName shouldBe fileName
actualPath shouldBe s"$basePath$fileName"
isDirectory shouldBe false
size shouldBe getLoremIpsum.length
System.currentTimeMillis().millis - lastModified.millis should be < 1.minute
perms should contain allOf (PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE)
}
}
}

"FtpIOSource" should {
Expand Down Expand Up @@ -90,9 +113,7 @@ trait CommonFtpSourceSpec extends BaseSpec {
probe.expectComplete()

val expectedNumOfBytes = getLoremIpsum.getBytes().length * numOfFiles
val total = result.foldLeft(0L) {
case (acc, next) => acc + next.count
}
val total = result.map(_.count).sum
total shouldBe expectedNumOfBytes
}
}
Expand Down

0 comments on commit 0a02acd

Please sign in to comment.