All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.jeroenr.tepkin.GridFs.scala Maven / Gradle / Ivy

The newest version!
package com.github.jeroenr.tepkin

import java.io._
import java.security.MessageDigest

import akka.actor.ActorRef
import akka.stream.Materializer
import akka.stream.scaladsl.{FileIO, Source}
import akka.util.{ByteString, Timeout}
import com.github.jeroenr.bson.BsonDsl._
import com.github.jeroenr.bson.Implicits._
import com.github.jeroenr.bson.element.BinarySubtype.Generic
import com.github.jeroenr.bson.element.{BinarySubtype, BsonObjectId}
import com.github.jeroenr.bson.util.Converters
import com.github.jeroenr.bson.{BsonDocument, BsonDsl, Implicits}
import com.github.jeroenr.tepkin.GridFs.Chunk
import com.github.jeroenr.tepkin.protocol.command.Index
import com.github.jeroenr.tepkin.protocol.result.DeleteResult
import org.joda.time.DateTime

import scala.concurrent.{ExecutionContext, Future}

class GridFs(db: MongoDatabase, prefix: String = "fs") {
  val chunkSize = 255 * 1024
  val files = db(s"$prefix.files")
  val chunks = db(s"$prefix.chunks")

  def findOne(query: BsonDocument)(implicit ec: ExecutionContext, timeout: Timeout): Future[Option[BsonDocument]] = {
    files.findOne(query)
  }

  /**
   * Puts the given file into GridFS.
   */
  def put(file: File)(implicit mat: Materializer, ec: ExecutionContext, timeout: Timeout): Future[BsonDocument] = {
    val fileId = BsonObjectId.generate
    val zero = (0, MessageDigest.getInstance("MD5"))

    FileIO.fromFile(file, chunkSize).runFold(zero) { case ((n, md), data) =>
      md.update(data.asByteBuffer)
      val chunk = Chunk(fileId = fileId, n = n, data = BsonValueBinary(data, Generic))
      chunks.insert(chunk.toDoc)
      (n + 1, md)
    }.flatMap { case (_, md) =>
      chunks.createIndexes(Index(key = ("files_id" := 1) ~ ("n" := 1), name = "files_id_n"))
      val md5 = Converters.hex2Str(md.digest())
      val document = {
        ("_id" := fileId) ~
          ("length" := file.length()) ~
          ("chunkSize" := chunkSize) ~
          ("uploadDate" := DateTime.now()) ~
          ("filename" := file.getName) ~
          ("md5" := md5)
      }
      files.insert(document).map(_ => document)
    }
  }

  /**
   * Puts the data from InputStream into GridFS.
   *
   * @param name name of the file
   */
  def put(name: String, input: InputStream)(implicit ec: ExecutionContext, timeout: Timeout): Future[BsonDocument] = {
    val buffer = new Array[Byte](chunkSize)
    val reader = new BufferedInputStream(input, chunkSize)
    val md = MessageDigest.getInstance("MD5")
    val fileId = BsonObjectId.generate

    val length = Iterator.continually(reader.read(buffer)).takeWhile(_ != -1).zipWithIndex.foldLeft(0) {
      case (total, (length, index)) =>
        md.update(buffer, 0, length)
        val chunk = Chunk(
          fileId = fileId,
          n = index,
          data = BsonValueBinary(ByteString.fromArray(buffer, 0, length), Generic))
        chunks.insert(chunk.toDoc)
        total + length
    }

    reader.close()

    chunks.createIndexes(Index(key = ("files_id" := 1) ~ ("n" := 1), name = "files_id_n"))

    val document = {
      ("_id" := fileId) ~
        ("length" := length) ~
        ("chunkSize" := chunkSize) ~
        ("uploadDate" := DateTime.now()) ~
        ("filename" := name) ~
        ("md5" := Converters.hex2Str(md.digest()))
    }
    files.insert(document).map(_ => document)
  }

  /**
   * Gets the file as a Chunk stream.
   *
   * @param id _id of the file
   */
  def get(id: BsonValueObjectId)
         (implicit timeout: Timeout): Source[Chunk, ActorRef] = {
    chunks.find($query("files_id" := id) ~ $orderBy("n" := 1)).mapConcat(_.map(Chunk.apply))
  }

  def getOne(query: BsonDocument)
            (implicit ec: ExecutionContext, timeout: Timeout): Future[Option[Source[Chunk, ActorRef]]] = {
    findOne(query).map {
      case Some(file) =>
        val id = file.get[BsonValueObjectId]("_id").get
        Some(get(id))

      case None => None
    }
  }

  /**
   * Deletes the specified file from GridFS storage.
   *
   * @param id _id of the file
   */
  def delete(id: BsonValueObjectId)(implicit ec: ExecutionContext, timeout: Timeout): Future[DeleteResult] = {
    for {
      deleteChunks <- chunks.delete("files_id" := id)
      deleteFile <- files.delete("_id" := id)
    } yield deleteFile
  }

  /** Deletes at most one file from GridFS storage matching the given criteria. */
  def deleteOne(query: BsonDocument)(implicit ec: ExecutionContext, timeout: Timeout): Future[DeleteResult] = {
    findOne(query).flatMap {
      case Some(file) =>
        delete(file.get[BsonValueObjectId]("_id").get)
      case None =>
        Future.successful(DeleteResult(ok = true, n = 0))
    }
  }
}

object GridFs {

  case class Chunk(id: BsonValueObjectId = BsonObjectId.generate,
                   fileId: BsonValueObjectId,
                   n: Int,
                   data: BsonValueBinary) {
    val toDoc: BsonDocument = {
      ("_id" := id) ~
        ("files_id" := fileId) ~
        ("n" := n) ~
        ("data" := data)
    }
  }

  object Chunk {
    def apply(document: BsonDocument): Chunk = {
      Chunk(
        id = document.get[BsonValueObjectId]("_id").get,
        fileId = document.get[BsonValueObjectId]("files_id").get,
        n = document.getAs[Int]("n").get,
        data = document.get[BsonValueBinary]("data").get)
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy