Skip to content

Instantly share code, notes, and snippets.

@almeidap
Last active September 14, 2020 13:32
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save almeidap/9660665 to your computer and use it in GitHub Desktop.
Save almeidap/9660665 to your computer and use it in GitHub Desktop.
DAO & Controller example for image manipulation & serving with ReactiveMongo (http://reactivemongo.org/) and Scrimage (https://github.com/sksamuel/scrimage). This code uses the DAO design for ReactiveMongo available here: https://gist.github.com/almeidap/5685801
import scala.concurrent.Future
import play.api.mvc._
import reactivemongo.api.gridfs.{ReadFile, GridFS}
import reactivemongo.bson.BSONValue
import core.dao.ImageDao
import enums.ImageSize
/**
* Controller for image action states.
*
* @author Pedro De Almeida (almeidap)
*/
object ImageApiController extends Controller {
def image(id: String, size: ImageSize.Value = ImageSize.Medium) = send(ImageDAO.findBySize(id, size))
/**
* Adapted from [[play.modules.reactivemongo.MongoController#serve]].
*/
def send[T <: ReadFile[_ <: BSONValue]](media: Future[Option[T]], dispositionMode: String = CONTENT_DISPOSITION_INLINE) = Action.async {
media.map(_.get).map { file =>
SimpleResult(
header = ResponseHeader(
OK,
Map(
CONTENT_LENGTH -> ("" + file.length),
CONTENT_DISPOSITION -> (s"""$dispositionMode; filename="${file.filename}"; filename*=UTF-8''""" + java.net.URLEncoder.encode(file.filename, "UTF-8").replace("+", "%20")),
CONTENT_TYPE -> file.contentType.getOrElse("application/octet-stream")
)
),
body = ImageDao.enumerate(file)
).withHeaders(("Cache-Control" -> "max-age=2592000"))
}.recover {
case _ => NotFound
}
}
}
import org.joda.time.DateTime
import scala.concurrent.Future
import play.api.libs.json.Json
import play.api.libs.iteratee.{Iteratee, Enumerator}
import play.api.Logger
import reactivemongo.bson.{BSONDocument, BSONValue}
import reactivemongo.api.gridfs.{DefaultFileToSave, ReadFile}
import com.sksamuel.scrimage.{Format, Image}
import core.dao.FileDAO
import core.db.MongoHelper
import enums.ImageSize
/* Implicits */
import reactivemongo.api.gridfs.Implicits._
/**
* DAO for image files of challenger evidences.
*
* @author Pedro De Almeida (almeidap)
*/
object ImageDAO extends FileDAO {
val collectionName = "images"
def findBySize(id: String, size: ImageSize.Value) = {
findOne(Json.obj("metadata" -> Json.obj("source" -> id, "size" -> size.name)))
}
def duplicate(id: String, size: ImageSize.Value): Future[Option[ReadFile[BSONValue]]] = {
Logger.warn(s"Duplicating image: [id=$id, size=$size]")
// Lookup original image:
ImageDAO.findById(id).flatMap {
result => result.map {
original => {
// Get an iterator for consumming the original file enumerator:
val iterator = ImageDAO.gfs.enumerate(original).run(Iteratee.consume[Array[Byte]]())
iterator.flatMap {
bytes => {
// Create resized image:
val enumerator: Enumerator[Array[Byte]] = Enumerator.outputStream(
out => {
Image(bytes).bound(size.max, size.max).writer(Format.JPEG).withCompression(90).write(out)
}
)
val data = DefaultFileToSave(
filename = original.filename,
contentType = original.contentType,
uploadDate = Some(DateTime.now().getMillis),
metadata = original.metadata ++ BSONDocument(
"source" -> MongoHelper.identify(original.id),
"size" -> size.name,
"max" -> size.max
)
)
Logger.warn(s"Saving resized image: [id=$id, metadata=${data.metadata}}]")
ImageDAO.gfs.save(enumerator, data).map {
image => Some(image)
}
}
}
}
}.getOrElse {
Logger.warn("Original image to duplicate could not be found, ignoring...")
Future.successful(None)
}
}
}
}
package enums
/**
* Enumeration for image sizes.
*
* @author Pedro De Almeida (almeidap)
*/
object ImageSize extends Enumeration {
case class ImageSizeValue(name: String, max: Int) extends Val(name)
val Original = ImageSizeValue("default", 0) // Original dimensions, no resizing
val Large = ImageSizeValue("large", 960)
val Medium = ImageSizeValue("medium", 480)
val Small = ImageSizeValue("small", 240)
val Icon = ImageSizeValue("thumb", 120)
implicit def valueToSize(v: Value): ImageSizeValue = v.asInstanceOf[ImageSizeValue]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment