Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Foto abruf und MimeType Erkennung implementiert

  • Loading branch information...
commit a994bb315944e8addab64049db243be360c29bbf 1 parent 29da93a
@KyleRogers authored
View
21 app/controllers/FotoVorfuehrer.scala
@@ -7,6 +7,7 @@ import model._
import com.google.inject._
import model.BenutzerName
import scala.Some
+import play.api.libs.iteratee.Enumerator
/**
*
@@ -31,12 +32,30 @@ class FotoVorfuehrer @Inject()(
}
private def fotoalbum(albumBesitzer: Benutzer)(implicit flash: Flash): Result = {
+ val foto: Foto = null
verwalter.findeFotoalbumVon(albumBesitzer) match {
- case Some(album) => Ok(views.html.foto(album))
+ case Some(album) => Ok(views.html.foto(album, foto))
case None => NotFound("Ungültiges Album")
}
}
+ def foto(id: Long) = StackAction { implicit request =>
+ val loadFotoFuture = scala.concurrent.Future { Foto.findeMitId(id) }
+ Async{
+ loadFotoFuture.map {
+ case Some(foto) => sendFotoResult(foto)
+ case None => NotFound("Angefordertes Foto nicht gefunden!")
+ }
+ }
+ }
+
+ private def sendFotoResult(foto: Foto): Result = {
+ val fileContent: Enumerator[Array[Byte]] = Enumerator(foto.content)
+ SimpleResult(
+ header = ResponseHeader(200, Map("Content-Type" -> foto.mimeType)),
+ body = fileContent
+ )
+ }
def hochladen = StackAction(parse.multipartFormData){ implicit request =>
val currentUser = loggedIn
View
50 app/model/Foto.scala
@@ -1,16 +1,64 @@
package model
import anorm._
+import anorm.SqlParser._
+import play.api.db.DB
+import play.api.Play.current
+import scala.collection.mutable
+import net.sf.jmimemagic.{MagicMatchNotFoundException, Magic}
/**
*
* @author Stefan Penndorf <stefan@cyphoria.net>
*/
-case class Foto(id: Pk[Long] = NotAssigned) {
+case class Foto(id: Pk[Long] = NotAssigned,
+ imageContent: mutable.WrappedArray[Byte] = new Array[Byte](11)) {
+
+ def mimeType = {
+ try {
+ Magic.getMagicMatch(content, true).getMimeType
+ } catch {
+ case _:MagicMatchNotFoundException => "application/octet-stream"
+ }
+ }
+
+ def content = imageContent.array
}
object Foto {
+ val simple = {
+ get[Long]("fotos.id") ~
+ get[Long]("fotos.besitzer") ~
+ bytes("fotos.foto") map {
+ case id~besitzer~foto => Foto(Id(id), foto)
+ }
+ }
+
+ implicit def rowToByteArray: Column[Array[Byte]] = Column.nonNull { (value, meta) => {
+ val MetaDataItem(qualified, _, _) = meta
+ value match {
+ case data: Array[Byte] => Right(data)
+ case cblob: java.sql.Blob => Right(cblob.getBytes(0, cblob.length.toInt))
+ case _ => Left(TypeDoesNotMatch("Cannot convert " + value + ":" + value.asInstanceOf[AnyRef].getClass + " to Byte Array for column " + qualified))
+ }
+ }
+ }
+
+ def bytes(columnName: String): RowParser[Array[Byte]] = get[Array[Byte]](columnName)(implicitly[Column[Array[Byte]]])
+
+ def findeMitId(id: Long): Option[Foto] = {
+ DB.withConnection { implicit connection =>
+ SQL(
+ """
+ SELECT id,besitzer,foto FROM fotos WHERE id={id}
+ """
+ ).on(
+ 'id -> id
+ ).as(simple.singleOpt)
+ }
+ }
+
}
View
4 app/views/foto.scala.html
@@ -1,4 +1,4 @@
-@(album: model.Fotoalbum)
+@(album: model.Fotoalbum, foto: model.Foto)
@import helper._
@import helper.twitterBootstrap._
@@ -11,7 +11,7 @@
<div>
<img style="display:block;float:right;" width="620px"
alt="Lukas hebt den Kopf: Ich will auch mit feiern!" title="Lukas hebt den Kopf: Ich will auch mit feiern!"
- src="@routes.Assets.at("images/lukas.jpg")">
+ src="@routes.FotoVorfuehrer.foto(foto.id.get)">
</div>
View
2  app/views/fotoalben.scala.html
@@ -30,7 +30,7 @@
<div class="clearfix">
<label for="bilddatei">Bild ausw&auml;hlen:</label>
<div class="input">
- <input type="file" size="50" name="bilddatei" id="bildDatei">
+ <input type="file" size="50" name="bilddatei" id="bildDatei" accept="image">
</div>
</div>
</fieldset>
View
1  conf/routes
@@ -23,6 +23,7 @@ GET /viparea controllers.VipArea.viparea
GET /fotoalben @controllers.FotoVorfuehrer.fotoalben
POST /fotoalben @controllers.FotoVorfuehrer.hochladen
GET /fotoalbum/:besitzer @controllers.FotoVorfuehrer.fotoalbum(besitzer: model.BenutzerName)
+GET /foto/:id @controllers.FotoVorfuehrer.foto(id: Long)
# ADMIN Area
GET /gaesteliste @controllers.AdminArea.gaesteliste
View
2  project/Build.scala
@@ -26,6 +26,8 @@ object ApplicationBuild extends Build {
"com.google.inject" % "guice" % "3.0",
"com.tzavellas" % "sse-guice" % "0.7.1",
+ "jmimemagic" % "jmimemagic" % "0.1.2",
+
// TEST DEPENDENCIES
"play" %% "play-test" % "2.1.2" % "test",
View
40 test/net/cyphoria/weddingapp/functional/FotoVorfuehrerControllerTest.scala
@@ -18,6 +18,7 @@ class FotoVorfuehrerControllerTest extends Specification with MockFactory {
object config extends WeddingAuthConfig
val einGast = KERSTIN
+
"FotoVorfuehrer" should {
"das Hochladen von Bildern ermoeglichen" in laufenderAnwendungMitScenario("einemGast") {
@@ -28,6 +29,45 @@ class FotoVorfuehrerControllerTest extends Specification with MockFactory {
contentAsString(result) must contain("""<input type="file"""")
}
+ "ein Foto mit korrektem Mime-Type für PNG zurück geben" in laufenderAnwendungMitScenario("einemGastMitEinemFoto") {
+ val result = route(FakeRequest(GET, "/foto/1").withLoggedIn(config)(einGast.id.get)).get
+
+ status(result) must equalTo(OK)
+ contentType(result) must beSome("image/png")
+ }
+
+ "eine Fehlermeldung anzeigen, wenn das angeforderte Foto nicht existiert" in laufenderAnwendungMitScenario("einemGast") {
+ val result = route(FakeRequest(GET, "/foto/999").withLoggedIn(config)(einGast.id.get)).get
+
+ status(result) must equalTo(NOT_FOUND)
+ contentAsString(result) must contain("Foto nicht gefunden")
+ }
+
+ "ein Foto mit korrektem Mime-Type für JPEG zurück geben" in laufenderAnwendungMitScenario("einemGastMitDreiFotos") {
+ val result = route(FakeRequest(GET, "/foto/2").withLoggedIn(config)(einGast.id.get)).get
+
+ status(result) must equalTo(OK)
+ contentType(result) must beSome("image/jpeg")
+ }
+
+
+ "ein Foto zurück geben" in laufenderAnwendungMitScenario("einemGastMitEinemFoto") {
+ val result = route(FakeRequest(GET, "/foto/1").withLoggedIn(config)(einGast.id.get)).get
+
+ status(result) must equalTo(OK)
+ contentAsBytes(result) must beEqualTo(PNG_IMAGE_CONTENT)
+ }
+
+ "die Fotoseite mit dem ersten Foto im Album anzeigen" in laufenderAnwendungMitScenario("einemGastMitEinemFoto") {
+ val result = route(FakeRequest(GET, "/fotoalbum/Kerstin.Albert").withLoggedIn(config)(einGast.id.get)).get
+
+ status(result) must equalTo(OK)
+ contentAsString(result) must contain("Fotoalbum von Kerstin")
+ contentAsString(result) must contain("src=\"/foto/1\"")
+
+ }
+
+
}
}
View
45 test/net/cyphoria/weddingapp/model/FotoTest.scala
@@ -0,0 +1,45 @@
+package net.cyphoria.weddingapp.model
+
+import org.specs2.mutable.Specification
+import model.Foto
+import anorm.Id
+
+/**
+ *
+ * @author Stefan Penndorf <stefan@cyphoria.net>
+ */
+class FotoTest extends Specification {
+
+ val UNFUG_CONTENT: Array[Byte] = "\u00FF\u0034\u0056ABCDE".toCharArray.map(_.toByte)
+ val OTHER_CONTENT: Array[Byte] = "\u0012\u0034\u0056ABCDE".toCharArray.map(_.toByte)
+
+
+ "Foto" should {
+
+ "kein Foto finden, wenn noch kein Foto hochgeladen wurde" in DatenbankMit("einemGast") {
+ Foto.findeMitId(1L) must beNone
+ }
+
+ "ein Foto finden, wenn ein Foto hochgeladen wurde" in DatenbankMit("einemGastMitEinemFoto") {
+ Foto.findeMitId(1L) must beSome(Foto(Id(1), PNG_IMAGE_CONTENT))
+ }
+
+ "den korrekten Mime-Type für PNG bestimmen" in {
+ Foto(imageContent = PNG_IMAGE_CONTENT).mimeType must beEqualTo("image/png")
+ }
+
+ "den korrekten Mime-Type für JPEG bestimmen" in {
+ Foto(imageContent = JPEG_IMAGE_CONTENT).mimeType must beEqualTo("image/jpeg")
+ }
+
+ "einen anderen Mime-Type für ungültige Daten bestimmen" in {
+ Foto(imageContent = OTHER_CONTENT).mimeType must not (beEqualTo("image/png") or beEqualTo("image/jpeg"))
+ }
+
+ "den Mime-Type 'application/octet-stream' bestimmen, wenn JMimeMagic keinen Treffer findet" in {
+ Foto(imageContent = UNFUG_CONTENT).mimeType must beEqualTo("application/octet-stream")
+ }
+
+ }
+
+ }
View
4 test/net/cyphoria/weddingapp/model/package.scala
@@ -13,6 +13,10 @@ package object model {
// TODO Refactor tests so dass Konstanten aus model benutzt werden.
val KERSTIN = new Benutzer(Id(1L), BenutzerName("Kerstin", "Albert"), "kerstin@cyphoria.net", Some("$2a$10$k5TmtHnitQvFCNAp8SbuFeq1VlhlcSGkXl6JAcwZFX20mRZKgEgm."))
+ val PNG_IMAGE_CONTENT: Array[Byte] = "\211PNG\u000D\u000A\u001A\u000AABCDE".toCharArray.map(_.toByte)
+ val JPEG_IMAGE_CONTENT: Array[Byte] = "\u00FF\u00D8\u00FFABCDE".toCharArray.map(_.toByte)
+
+
def mitDatenbank[T](block: => T) = laufenderAnwendung(block)
def DatenbankMit[T](fixtureFileName: String)(block: => T) = laufenderAnwendungMitScenario(fixtureFileName)(block)
View
5 test/net/cyphoria/weddingapp/templates/FotoTemplateTest.scala
@@ -3,7 +3,8 @@ package net.cyphoria.weddingapp.templates
import org.specs2.mutable.Specification
import play.api.test.Helpers._
import play.api.test.FakeApplication
-import model.Fotoalbum
+import model.{Foto, Fotoalbum}
+import anorm.Id
/**
*
@@ -12,7 +13,7 @@ import model.Fotoalbum
class FotoTemplateTest extends Specification {
val foto = running(FakeApplication(additionalConfiguration = inMemoryDatabase())) {
- views.html.foto(Fotoalbum(TERESA, 1))
+ views.html.foto(Fotoalbum(TERESA, 1), Foto(Id(1)))
}
"Die Fotoansicht" should {
View
6 test/resources/model/einemGastMitDreiFotos.dbt
@@ -2,6 +2,6 @@ users:
- id: 1, email: "kerstin@cyphoria.net", vorname: "Kerstin", nachname: "Albert", passwort: "$2a$10$k5TmtHnitQvFCNAp8SbuFeq1VlhlcSGkXl6JAcwZFX20mRZKgEgm."
fotos:
-- id: 1, besitzer: 1, foto: "0123"
-- id: 2, besitzer: 1, foto: "4567"
-- id: 3, besitzer: 1, foto: "7890"
+- id: 1, besitzer: 1, foto: "89504E470D0A1A0A4142434445"
+- id: 2, besitzer: 1, foto: "FFD8FF4142434445"
+- id: 3, besitzer: 1, foto: "89504E470D0A1A0A4142434445"
View
2  test/resources/model/einemGastMitEinemFoto.dbt
@@ -2,4 +2,4 @@ users:
- id: 1, email: "kerstin@cyphoria.net", vorname: "Kerstin", nachname: "Albert", passwort: "$2a$10$k5TmtHnitQvFCNAp8SbuFeq1VlhlcSGkXl6JAcwZFX20mRZKgEgm."
fotos:
-- id: 1, besitzer: 1, foto: "0123"
+- id: 1, besitzer: 1, foto: "89504E470D0A1A0A4142434445"
Please sign in to comment.
Something went wrong with that request. Please try again.