Search code examples
angularjsplayframeworkplayframework-2.0ng-file-uploadplayframework-2.5

Uploading file from Angular ng-file-upload to Play for Scala


I'm trying to upload a .csv file using ng-file-upload in the browser and Play for Scala 2.5.x at the backend.

If I use moveTo in Play the file is uploaded, but generated with headers and trailers instead of the plain file:

request.body.moveTo(new File("c:\\tmp\\uploaded\\filetest.csv"))

Is there a way to save only the data part?

Alternatively, I tried the following

 def doUpload  = Action(parse.multipartFormData)  { request =>


    println(request.contentType.get)
    println(request.body)

   request.body.file("file").map { file =>
      val filename = file.filename
      val contentType = file.contentType
      file.ref.moveTo(new File("c:\\tmp\\uploaded\\filetest.csv"))
    }

    Ok("file uploaded at " + new java.util.Date())

 }

But the map is empty. This is what I get in the first two println statements:

multipart/form-data MultipartFormData(Map(),Vector(FilePart(file,hello2.csv,Some(application/vnd.ms-excel),TemporaryFile(C:\Users\BUSINE~1\AppData\Local\Temp\playtemp2236977636541678879\multipartBody2268668511935303172asTemporaryFile))),Vector())

Meaning that I am receiving something, however I cannot extract it. Any ideas?


Solution

  • A bit verbose maybe but it does what you want.
    Note that I am on a Mac so you may have to change the /tmp/uploaded/ path in copyFile since it looks like you are on Windows.

    package controllers
    
    import java.io.File
    import java.nio.file.attribute.PosixFilePermission._
    import java.nio.file.attribute.PosixFilePermissions
    import java.nio.file.{Files, Path}
    import java.util
    import javax.inject._
    
    import akka.stream.IOResult
    import akka.stream.scaladsl._
    import akka.util.ByteString
    import play.api._
    import play.api.data.Form
    import play.api.data.Forms._
    import play.api.i18n.MessagesApi
    import play.api.libs.streams._
    import play.api.mvc.MultipartFormData.FilePart
    import play.api.mvc._
    import play.core.parsers.Multipart.FileInfo
    
    import scala.concurrent.Future
    import java.nio.file.StandardCopyOption._
    import java.nio.file.Paths
    
    case class FormData(filename: String)
    
    // Type for multipart body parser     
    type FilePartHandler[A] = FileInfo => Accumulator[ByteString, FilePart[A]]
    
    val form = Form(mapping("file" -> text)(FormData.apply)(FormData.unapply))
    
    private def deleteTempFile(file: File) = Files.deleteIfExists(file.toPath)
    
    // Copies temp file to your loc with provided name
    private def copyFile(file: File, name: String) =
        Files.copy(file.toPath(), Paths.get("/tmp/uploaded/", ("copy_" + name)), REPLACE_EXISTING)
    
    // FilePartHandler which returns a File, rather than Play's TemporaryFile class
    private def handleFilePartAsFile: FilePartHandler[File] = {
        case FileInfo(partName, filename, contentType) =>
          val attr = PosixFilePermissions.asFileAttribute(util.EnumSet.of(OWNER_READ, OWNER_WRITE))
          val path: Path = Files.createTempFile("multipartBody", "tempFile", attr)
          val file = path.toFile
          val fileSink: Sink[ByteString, Future[IOResult]] = FileIO.toPath(file.toPath())
          val accumulator: Accumulator[ByteString, IOResult] = Accumulator(fileSink)
          accumulator.map {
            case IOResult(count, status) =>
              FilePart(partName, filename, contentType, file)
          } (play.api.libs.concurrent.Execution.defaultContext)
      }
    
    // The action
    def doUpload = Action(parse.multipartFormData(handleFilePartAsFile)) { implicit request =>
        val fileOption = request.body.file("file").map {
          case FilePart(key, filename, contentType, file) =>
            val copy = copyFile(file, filename)
            val deleted = deleteTempFile(file)  // delete original uploaded file after we have 
            copy   
        }
        Ok(s"Uploaded: ${fileOption}")
    }