Skip to content
Browse files

Einzelfotoanzeige bzw. Abruf vervollständigt

  • Loading branch information...
1 parent a994bb3 commit f703feab263fa2af2f071d1e1c7ee6321b201abd @KyleRogers committed Oct 15, 2013
View
35 app/controllers/FotoVorfuehrer.scala
@@ -2,7 +2,6 @@ package controllers
import play.api.mvc._
import jp.t2v.lab.play2.auth.AuthenticationElement
-import java.io.File
import model._
import com.google.inject._
import model.BenutzerName
@@ -25,17 +24,21 @@ class FotoVorfuehrer @Inject()(
}
def fotoalbum(besitzerName: BenutzerName): Action[AnyContent] = StackAction{ implicit request =>
- gästeliste.findeGastMitName(besitzerName) match {
- case Some(besitzer) => fotoalbum(besitzer)
- case None => NotFound("Ungültiges Album")
+ val findFotoalbumTask = scala.concurrent.Future {
+ gästeliste.findeGastMitName(besitzerName)
+ }.map {
+ case Some(besitzer) => besitzer.fotoalbum
+ case None => None
+ }.map {
+ case Some(album) => Some((album, album.erstesFoto))
+ case None => None
}
- }
- 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, foto))
- case None => NotFound("Ungültiges Album")
+ Async {
+ findFotoalbumTask.map {
+ case Some((album, foto)) => Ok(views.html.foto(album, foto))
+ case None => NotFound("Ungültiges Album")
+ }
}
}
@@ -52,21 +55,17 @@ class FotoVorfuehrer @Inject()(
private def sendFotoResult(foto: Foto): Result = {
val fileContent: Enumerator[Array[Byte]] = Enumerator(foto.content)
SimpleResult(
- header = ResponseHeader(200, Map("Content-Type" -> foto.mimeType)),
+ header = ResponseHeader(200, Map(
+ "Content-Type" -> foto.mimeType
+ )),
body = fileContent
)
}
def hochladen = StackAction(parse.multipartFormData){ implicit request =>
val currentUser = loggedIn
request.body.file("bilddatei").map { picture =>
- val tempfile = File.createTempFile("pic", "png")
- try {
- picture.ref.moveTo(tempfile, replace = true)
- fotoImporter.importiere(tempfile, currentUser)
- } finally {
- tempfile.delete()
- }
+ fotoImporter.importiere(picture.ref.file, currentUser)
Redirect(routes.FotoVorfuehrer.fotoalben()).flashing(
"erfolgsMeldung" -> "Bild erfolgreich zu deinem Album hinzugefügt."
View
4 app/model/Benutzer.scala
@@ -50,6 +50,10 @@ case class Benutzer(
def istVIP(): Boolean = {
passwort.isDefined
}
+
+ def fotoalbum: Option[Fotoalbum] = Fotoalbum.findeFotoalbumVon(this)
+
+
}
object Benutzer {
View
5 app/model/FotoImporter.scala
@@ -2,6 +2,7 @@ package model
import com.google.inject._
import java.io.File
+import scalax.io._
/**
*
@@ -18,8 +19,6 @@ class FotoImporterImpl @Inject()(
verwalter: FotoalbenVerwalter
) extends FotoImporter {
def importiere(bild: File, besitzer: Benutzer) {
- val fotoalbum = verwalter.findeFotoalbumVon(besitzer).getOrElse(Fotoalbum(besitzer, 0))
-
- verwalter.speichereFoto(new Array[Byte](2), fotoalbum)
+ verwalter.speichereFotoFuerBenutzer(Resource.fromFile(bild).byteArray, besitzer)
}
}
View
60 app/model/Fotoalbum.scala
@@ -15,13 +15,48 @@ case class Fotoalbum(
anzahlFotos: Long
) {
+ lazy val erstesFoto: Foto = {
+ DB.withConnection { implicit connection =>
+ SQL(
+ """
+ SELECT id,besitzer,foto FROM fotos f
+ WHERE f.besitzer = {besitzerId}
+ ORDER BY ID ASC
+ LIMIT 1
+ """
+ ).on(
+ 'besitzerId -> besitzer.id
+ ).as(Foto.simple.single)
+ }
+
+ }
+
+}
+
+object Fotoalbum {
+
+ def findeFotoalbumVon(besitzer: Benutzer): Option[Fotoalbum] = {
+ DB.withConnection { implicit connection =>
+ SQL(
+ """
+ SELECT COUNT(f.id) as anzahlFotos FROM fotos f
+ WHERE f.besitzer = {besitzerId}
+ HAVING COUNT(f.id) > 0
+ """
+ ).on(
+ 'besitzerId -> besitzer.id
+ ).as(long("anzahlFotos").singleOpt) map {
+ case anzahlFotos => Fotoalbum(besitzer, anzahlFotos)
+ }
+ }
+ }
+
+
}
- // TODO CLEAN UP ALL OTHER STUFF USING GÄSTELISTE TO USE FOTOALBUM
trait FotoalbenVerwalter {
def alleFotoalben(): List[Fotoalbum]
- def findeFotoalbumVon(besitzer: Benutzer): Option[Fotoalbum]
- def speichereFoto(foto: Array[Byte], fotoalbum: Fotoalbum)
+ def speichereFotoFuerBenutzer(foto: Array[Byte], besitzer: Benutzer)
}
class PersistenterFotoalbenVerwalter extends FotoalbenVerwalter {
@@ -40,7 +75,7 @@ class PersistenterFotoalbenVerwalter extends FotoalbenVerwalter {
}
}
- def speichereFoto(foto: Array[Byte], fotoalbum: Fotoalbum) {
+ def speichereFotoFuerBenutzer(foto: Array[Byte], besitzer: Benutzer) {
DB.withConnection { implicit connection =>
SQL(
"""
@@ -49,25 +84,10 @@ class PersistenterFotoalbenVerwalter extends FotoalbenVerwalter {
({besitzerid}, {foto})
"""
).on(
- 'besitzerid -> fotoalbum.besitzer.id,
+ 'besitzerid -> besitzer.id,
'foto -> foto
).executeUpdate()
}
}
- def findeFotoalbumVon(besitzer: Benutzer): Option[Fotoalbum] = {
- DB.withConnection { implicit connection =>
- SQL(
- """
- SELECT COUNT(f.id) as anzahlFotos FROM fotos f
- WHERE f.besitzer = {besitzerId}
- HAVING COUNT(f.id) > 0
- """
- ).on(
- 'besitzerId -> besitzer.id
- ).as(long("anzahlFotos").singleOpt) map {
- case anzahlFotos => Fotoalbum(besitzer, anzahlFotos)
- }
- }
- }
}
View
5 project/Build.scala
@@ -17,8 +17,8 @@ object ApplicationBuild extends Build {
"com.typesafe" %% "play-plugins-mailer" % "2.1.0",
- "org.slf4j" % "jul-to-slf4j" % "1.6.6",
- "ch.qos.logback" % "logback-classic" % "0.9.11",
+ "org.slf4j" % "jul-to-slf4j" % "1.7.2",
+ "ch.qos.logback" % "logback-classic" % "1.0.7",
"jp.t2v" %% "play2.auth" % "0.9",
"org.mindrot" % "jbcrypt" % "0.3m",
@@ -27,6 +27,7 @@ object ApplicationBuild extends Build {
"com.tzavellas" % "sse-guice" % "0.7.1",
"jmimemagic" % "jmimemagic" % "0.1.2",
+ "xml-apis" % "xml-apis" % "1.4.01" force(),
// TEST DEPENDENCIES
View
48 test/net/cyphoria/weddingapp/imagecompare/FileDownloader.scala
@@ -0,0 +1,48 @@
+package net.cyphoria.weddingapp.imagecompare
+
+import java.io.File
+import java.net.URL
+import org.apache.http.client.methods.HttpGet
+import org.apache.http.impl.client.DefaultHttpClient
+import org.apache.http.client.params.{CookiePolicy, ClientPNames}
+import org.apache.http.impl.cookie.BasicClientCookie
+import org.apache.http.client.CookieStore
+import org.openqa.selenium.WebDriver
+import scala.collection.JavaConverters._
+import org.apache.commons.io.FileUtils
+import org.apache.http.cookie.ClientCookie
+
+/**
+ *
+ * @author Stefan Penndorf <stefan@cyphoria.net>
+ */
+class FileDownloader(val webdriver: WebDriver) {
+
+ def downloadFile(source: URL, target: File) {
+ val client = new DefaultHttpClient()
+ client.getParams.setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BROWSER_COMPATIBILITY)
+
+ mimicCookies(webdriver.manage().getCookies.asScala, client.getCookieStore)
+
+ val content = client.execute(new HttpGet(source.toURI)).getEntity.getContent
+
+ FileUtils.copyInputStreamToFile(content, target)
+ }
+
+ def mimicCookies(seleniumCookies: Iterable[org.openqa.selenium.Cookie], store: CookieStore) = {
+ seleniumCookies.map(toCommonsCookie).foreach(store.addCookie(_))
+ }
+
+ def toCommonsCookie(keks: org.openqa.selenium.Cookie): BasicClientCookie = {
+ val cookie = new BasicClientCookie(keks.getName, keks.getValue)
+ cookie.setVersion(1)
+ cookie.setDomain(keks.getDomain)
+ cookie.setPath(keks.getPath)
+
+ cookie.setAttribute(ClientCookie.VERSION_ATTR, "1")
+ cookie.setAttribute(ClientCookie.DOMAIN_ATTR, keks.getDomain)
+ cookie
+ }
+
+
+}
View
2 test/net/cyphoria/weddingapp/imagecompare/ImageComparator.scala
@@ -86,7 +86,7 @@ object ImageComparator {
}
def imagesAreEqual(referenceImage: BufferedImage, actualImage: BufferedImage): Boolean = {
- haveSameSize(referenceImage, actualImage) && haveSameContent(referenceImage, actualImage)
+ actualImage != null && haveSameSize(referenceImage, actualImage) && haveSameContent(referenceImage, actualImage)
}
}
View
42 test/net/cyphoria/weddingapp/model/FotoImporterImplTest.scala
@@ -1,31 +1,53 @@
package net.cyphoria.weddingapp.model
import org.specs2.mutable.Specification
-import org.scalamock.specs2.MockFactory
-import model.{Fotoalbum, FotoalbenVerwalter, PersistenteGästeliste, FotoImporterImpl}
+import org.specs2.mock._
+import model._
+import org.specs2.mock.mockito.ArgThat
+import org.specs2.matcher.{MatchResult, Expectable, Matcher}
/**
*
* @author Stefan Penndorf <stefan@cyphoria.net>
*/
-class FotoImporterImplTest extends Specification with MockFactory {
+class FotoImporterImplTest extends Specification with Mockito with ArgThat {
val einGast = KERSTIN
"FotoImporter" should {
- val fotoalbum: Fotoalbum = Fotoalbum(einGast, 0)
-
"ein neues Foto zum Album des Benutzers hinzufügen, wenn ein Foto hochgeladen wird" in DatenbankMit("einemGast") {
- val gästeliste = new PersistenteGästeliste()
val verwalter = mock[FotoalbenVerwalter]
val importer: FotoImporterImpl = new FotoImporterImpl(verwalter)
- (verwalter.findeFotoalbumVon _).expects(einGast).returns(Option(fotoalbum)).anyNumberOfTimes()
- (verwalter.speichereFoto _).expects(*, fotoalbum)
- importer.importiere(null, einGast)
+ importer.importiere(JPEG_BILD_DATEI, einGast)
+
+ there was one(verwalter).speichereFotoFuerBenutzer(any[Array[Byte]], argThat(===(einGast)))
}
- }
+ "den Inhalt des Foto speichern, wenn ein Foto hochgeladen wird" in DatenbankMit("einemGast") {
+ val verwalter = mock[FotoalbenVerwalter]
+ val importer: FotoImporterImpl = new FotoImporterImpl(verwalter)
+
+ importer.importiere(JPEG_BILD_DATEI, einGast)
+ there was one(verwalter).speichereFotoFuerBenutzer(arrayStartsWithByteSequence(JPEG_IMAGE_CONTENT), ===(einGast))
+ }
+
+ def arrayStartsWithByteSequence(head: Array[Byte]) = new ArrayStartWithByteSequence(head)
+
+ class ArrayStartWithByteSequence(val head: Array[Byte]) extends Matcher[Array[Byte]] {
+ val HEAD_STRING = toString(head)
+ def apply[S <: Array[Byte]](s: Expectable[S]): MatchResult[S] = {
+ result(
+ s.value.startsWith(head),
+ f"Array[Byte] start with $HEAD_STRING%s",
+ f"expected Array[Byte] start with $HEAD_STRING%s but found " + toString(s.value),
+ s)
+ }
+
+ private def toString(arr: Array[Byte]) = arr.take(head.length + 3).map("%02X" format _).mkString
+ }
+
+ }
}
View
29 test/net/cyphoria/weddingapp/model/FotoalbumTest.scala
@@ -1,14 +1,28 @@
package net.cyphoria.weddingapp.model
import org.specs2.mutable.Specification
-import model.{Fotoalbum, PersistenterFotoalbenVerwalter}
+import model.{Foto, Fotoalbum, PersistenterFotoalbenVerwalter}
+import anorm.Id
/**
*
* @author Stefan Penndorf <stefan@cyphoria.net>
*/
class FotoalbumTest extends Specification {
+ val einGast = KERSTIN
+
+ "Fotoalbum eines Gastes" should {
+ "das erste Foto zurück geben" in DatenbankMit("einemGastMitEinemFoto") {
+ einGast.fotoalbum.get.erstesFoto must beEqualTo(Foto(Id(1), PNG_IMAGE_CONTENT))
+ }
+
+ "das Foto mit der kleinsten ID als erstes Foto zurück geben, selbst wenn drei Fotos hochgeladen wurden" in DatenbankMit("einemGastMitDreiFotos") {
+ einGast.fotoalbum.get.erstesFoto must beEqualTo(Foto(Id(1), PNG_IMAGE_CONTENT))
+ }
+
+ }
+
val verwalter = new PersistenterFotoalbenVerwalter()
"PersistenterFotoalbenVerwalter" should {
@@ -17,27 +31,14 @@ class FotoalbumTest extends Specification {
verwalter.alleFotoalben() must beEmpty
}
- "Kerstins Fotoalbum nicht finden, wenn noch kein Foto hochgeladen wurde" in DatenbankMit("einemGast") {
- verwalter.findeFotoalbumVon(KERSTIN) must beNone
- }
-
-
"findet alle Fotoalben von Benutzern, die ein Foto hochgeladen haben" in DatenbankMit("einemGastMitEinemFoto") {
verwalter.alleFotoalben().map(_.besitzer) must contain(KERSTIN)
}
- "Kerstins Fotoalbum finden, wenn Sie ein Foto hochgeladen hat" in DatenbankMit("einemGast") {
- verwalter.speichereFoto(new Array[Byte](3), Fotoalbum(KERSTIN, 0))
- verwalter.findeFotoalbumVon(KERSTIN) must beSome(Fotoalbum(KERSTIN, 1))
- }
-
-
"Kerstins Fotoalbum mit drei Fotos finden, wenn Sie drei Fotos hochgeladen hat" in DatenbankMit("einemGastMitDreiFotos") {
- verwalter.findeFotoalbumVon(KERSTIN) must beSome(Fotoalbum(KERSTIN, 3))
verwalter.alleFotoalben() must contain(Fotoalbum(KERSTIN, 3))
}
-
}
}
View
15 test/net/cyphoria/weddingapp/model/GastModelTest.scala
@@ -9,7 +9,6 @@ import model._
*/
class GastModelTest extends Specification {
-// val einGast = Benutzer(Id(1L), BenutzerName("Kerstin", "Albert"), "kerstin@cyphoria.net", Some("$2a$10$k5TmtHnitQvFCNAp8SbuFeq1VlhlcSGkXl6JAcwZFX20mRZKgEgm."))
val einGast = KERSTIN
val gästeliste = new PersistenteGästeliste
@@ -49,6 +48,20 @@ class GastModelTest extends Specification {
"sich nicht authentifizieren können, wenn das Passwort falsch ist" in DatenbankMit("einemGast") {
Benutzer.authentifiziere(einGast.email, passwort = "falsch") must beNone
}
+
+ "kein Fotoalbum haben, wenn er noch kein Foto hochgeladen hat" in DatenbankMit("einemGast") {
+ einGast.fotoalbum must beNone
+ }
+
+ "ein Fotoalbum mit einem Foto haben, wenn er ein Foto hochgeladen hat" in DatenbankMit("einemGastMitEinemFoto") {
+ einGast.fotoalbum must beSome(Fotoalbum(einGast, 1))
+ }
+
+ "ein Fotoalbum mit drei Fotos haben, wenn er drei Fotos hochgeladen hat" in DatenbankMit("einemGastMitDreiFotos") {
+ einGast.fotoalbum must beSome(Fotoalbum(einGast, 3))
+ }
+
+
}
View
9 test/net/cyphoria/weddingapp/model/package.scala
@@ -3,6 +3,8 @@ package net.cyphoria.weddingapp
import _root_.model.{BenutzerName, Benutzer}
import net.cyphoria.weddingapp.functional._
import anorm.Id
+import org.springframework.core.io.ClassPathResource
+import java.io.File
/**
*
@@ -14,7 +16,12 @@ package object model {
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)
+ val JPEG_IMAGE_CONTENT: Array[Byte] = "\u00FF\u00D8\u00FF\u00E0\u0000\u0010\u004A\u0046\u0049\u0046".toCharArray.map(_.toByte)
+
+
+
+ val JPEG_BILD_DATEI: File = new ClassPathResource("images/mara_und_lukas.jpg").getFile
+
def mitDatenbank[T](block: => T) = laufenderAnwendung(block)
View
17 test/net/cyphoria/weddingapp/specification/seiten/FotoalbenSeite.scala
@@ -11,6 +11,8 @@ import java.net.URL
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
import net.cyphoria.weddingapp.imagecompare.scalatest.ImageCompareMatchers
+import java.io.File
+import net.cyphoria.weddingapp.imagecompare.FileDownloader
/**
@@ -53,12 +55,25 @@ class FotoalbenSeite extends FluentPage with ShouldMatchers with ImageCompareMat
private def aktuellesBild: BufferedImage = {
val imageSrc = $("img").first().getAttribute("src")
- ImageIO.read(new URL(imageSrc))
+ imageSrc should include("foto")
+
+ val target = File.createTempFile("image", "")
+ // Das ist schlecht und unsicher und sollte durch eine bessere TEMP-Verwaltung ersetzt werden
+ target.deleteOnExit()
+
+ new FileDownloader(getDriver).downloadFile(new URL(imageSrc), target)
+
+ ImageIO.read(target)
}
override def getUrl: String = "/fotoalben"
override def isAt() {
+ await().atMost(3, TimeUnit.SECONDS).until(new Predicate[WebDriver] {
+ def apply(p1: WebDriver): Boolean = {
+ title().contains("Steffi und Stefan heiraten!")
+ }
+ })
title() should be ("Steffi und Stefan heiraten!")
$("h1").getText should equal ("Fotoalben")
}
View
4 test/resources/net/cyphoria/weddingapp/specification/fotoalbum.feature
@@ -7,8 +7,8 @@ Die Gäste können Fotoalben anschauen und selbst Bilder in ein Fotoalbum hochla
Und Kerstin hat sich angemeldet
@current
- Szenario: Fotos können hochgeladen werden.
+ Szenario: Fotos hochladen
Angenommen Kerstin ruft die Fotoalben auf
Wenn sie ein Bild hochlädt
Dann wird ein Fotoalbum für sie erstellt
- Und kann sie das Foto anschauen
+ Und kann sie das Foto anschauen

0 comments on commit f703fea

Please sign in to comment.
Something went wrong with that request. Please try again.