Last active
September 14, 2020 13:32
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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