--- a/src/main/scala/Filesystem.scala Wed Apr 11 14:56:25 2012 +0200
+++ b/src/main/scala/Filesystem.scala Fri Apr 13 17:43:15 2012 +0200
@@ -17,50 +17,54 @@
baseDirectory: File,
val basePath: String,
val lang: Lang)(mode: RWWMode) extends ResourceManager {
-
+
val logger: Logger = LoggerFactory.getLogger(this.getClass)
-
+
def sanityCheck(): Boolean =
baseDirectory.exists && baseDirectory.isDirectory
def resource(url: URL): Resource = new Resource {
def name() = url
- val relativePath: String = url.getPath.replaceAll("^"+basePath.toString+"/?", "")
+
+ val relativePath: String = url.getPath.replaceAll("^" + basePath.toString + "/?", "")
val fileOnDisk = new File(baseDirectory, relativePath)
lazy val parent = fileOnDisk.getParentFile
-
+
private def parentMustExist(): Unit = {
val parent = fileOnDisk.getParentFile
- if (! parent.exists) sys.error("Parent directory %s does not exist" format parent.getAbsolutePath)
- if (! parent.isDirectory) sys.error("Parent %s is not a directory" format parent.getAbsolutePath)
+ if (!parent.exists) sys.error("Parent directory %s does not exist" format parent.getAbsolutePath)
+ if (!parent.isDirectory) sys.error("Parent %s is not a directory" format parent.getAbsolutePath)
}
-
+
private def createDirectoryOnDisk(): Unit = {
parentMustExist()
val r = fileOnDisk.mkdir()
if (!r) sys.error("Could not create %s" format fileOnDisk.getAbsolutePath)
- logger.debug("%s successfully created: %s" format (fileOnDisk.getAbsolutePath, r.toString))
+ logger.debug("%s successfully created: %s" format(fileOnDisk.getAbsolutePath, r.toString))
}
-
+
private def createFileOnDisk(): Unit = {
parentMustExist()
val r = fileOnDisk.createNewFile()
- logger.debug("%s successfully created: %s" format (fileOnDisk.getAbsolutePath, r.toString))
+ logger.debug("%s successfully created: %s" format(fileOnDisk.getAbsolutePath, r.toString))
}
-
+
def get(unused: CacheControl.Value = CacheControl.CacheFirst): Validation[Throwable, Model] = {
val model = ModelFactory.createDefaultModel()
+ //for files: other possible ontologies to use would be
+ // "Linked Data Basic Profile 1.0" http://www.w3.org/Submission/2012/02/
+ // "the posix ontology" used by data.fm http://www.w3.org/ns/posix/stat#
if (fileOnDisk.isDirectory) {
val sioc = "http://rdfs.org/sioc/ns#"
val dirRes = model.createResource(name.toString)
- dirRes.addProperty(RDF.`type`,model.createResource(sioc+"Container"))
- for ( f <- fileOnDisk.listFiles() ) {
- val furl = new URL(name,f.getName)
- val item =model.createResource(furl.toString)
- dirRes.addProperty(model.createProperty(sioc+"container_of"),item)
- if (f.isDirectory) item.addProperty(RDF.`type`,model.createResource(sioc+"Container"))
- else item.addProperty(RDF.`type`,model.createResource(sioc+"Item"))
+ dirRes.addProperty(RDF.`type`, model.createResource(sioc + "Container"))
+ for (f <- fileOnDisk.listFiles()) {
+ val furl = new URL(name, f.getName)
+ val item = model.createResource(furl.toString)
+ dirRes.addProperty(model.createProperty(sioc + "container_of"), item)
+ if (f.isDirectory) item.addProperty(RDF.`type`, model.createResource(sioc + "Container"))
+ else item.addProperty(RDF.`type`, model.createResource(sioc + "Item"))
}
model.success
} else {
@@ -89,7 +93,29 @@
}
}
}
-
+
+ def getStream = try {
+ new BufferedInputStream(new FileInputStream(fileOnDisk)).success
+ } catch {
+ case fe: FileNotFoundException => fe.fail
+ case se: SecurityException => se.fail
+ }
+
+ def putStream(in: InputStream): Validation[Throwable, Unit] = {
+ val out = new FileOutputStream(fileOnDisk)
+ val buf = new Array[Byte](4096)
+ try {
+ val good = Iterator continually in.read(buf) takeWhile (-1 !=) foreach { bytesRead =>
+ out.write(buf,0,bytesRead)
+ }
+ good.success
+ } catch {
+ case ioe: IOException => ioe.fail
+ }
+
+ }
+
+
def save(model: Model): Validation[Throwable, Unit] =
try {
parent.mkdirs()
@@ -101,7 +127,7 @@
case t => t.fail
}
- def createDirectory(model: Model): Validation[Throwable, Unit] =
+ def createDirectory: Validation[Throwable, Unit] =
try {
createDirectoryOnDisk().success
// val fos = new FileOutputStream(fileOnDisk)
@@ -112,26 +138,27 @@
case t => t.fail
}
- def delete : Validation[Throwable, Unit]= try {
- Files.delete(fileOnDisk.toPath).success
+ def delete: Validation[Throwable, Unit] = try {
+ Files.delete(fileOnDisk.toPath).success
} catch {
case e: IOException => e.fail
}
- def create(): Validation[Throwable, Resource] = {
- if (!fileOnDisk.exists())
- new Throwable("Must first create "+name()).fail
- else if (!fileOnDisk.isDirectory)
- new Throwable("Can only create a resource in a directory/collection which this is not "+name()).fail
- else try {
- val path = Files.createTempFile(fileOnDisk.toPath,"res",lang.suffix)
- resource(new URL(name(),path.getFileName.toString)).success
- } catch {
- case ioe: IOException => ioe.fail
- }
+ def create(): Validation[Throwable, Resource] = {
+ if (!fileOnDisk.exists())
+ new Throwable("Must first create " + name()).fail
+ else if (!fileOnDisk.isDirectory)
+ new Throwable("Can only create a resource in a directory/collection which this is not " + name()).fail
+ else try {
+ val path = Files.createTempFile(fileOnDisk.toPath, "res", lang.suffix)
+ resource(new URL(name(), path.getFileName.toString)).success
+ } catch {
+ case ioe: IOException => ioe.fail
+ }
}
-
}
+
+}
-}
+
--- a/src/main/scala/GraphCache.scala Wed Apr 11 14:56:25 2012 +0200
+++ b/src/main/scala/GraphCache.scala Fri Apr 13 17:43:15 2012 +0200
@@ -30,13 +30,13 @@
import scalaz.{Scalaz, Validation}
import java.util.concurrent.TimeUnit
import com.google.common.cache.{LoadingCache, CacheLoader, CacheBuilder, Cache}
-import java.io.{File, FileOutputStream}
import com.weiglewilczek.slf4s.Logging
import javax.net.ssl.SSLContext
import org.apache.http.conn.scheme.Scheme
import java.security.NoSuchAlgorithmException
import org.apache.http.conn.ssl.{TrustStrategy, SSLSocketFactory}
import java.security.cert.X509Certificate
+import java.io.{InputStream, File, FileOutputStream}
/**
@@ -118,12 +118,15 @@
//perhaps the resource manager should be split into read/write sections?
def save(model: Model) = throw new MethodNotSupportedException("not implemented")
- def createDirectory(model: Model) = throw new MethodNotSupportedException("not implemented")
+ def createDirectory = throw new MethodNotSupportedException("not implemented")
def delete = throw new MethodNotSupportedException("not implemented")
def create() = throw new MethodNotSupportedException("not implemented")
+ def getStream = throw new MethodNotSupportedException("not implemented")
+
+ def putStream(in: InputStream) = throw new MethodNotSupportedException("not implemented")
}
private def getUrl(u: URL) = {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/Image.scala Fri Apr 13 17:43:15 2012 +0200
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2012 Henry Story (bblfish.net)
+ * under the MIT licence defined at
+ * http://www.opensource.org/licenses/mit-license.html
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in the
+ * Software without restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the
+ * following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package org.w3.readwriteweb
+
+/**
+ * Image mime types adapted in a hurry from the Lang class.
+ *
+ * It really feels like oneshould have one whole set of mime type classes that cover all the cases.
+ *
+ * @author bblfish
+ * @created 12/04/2012
+ */
+
+sealed trait Image {
+
+ def suffix = this match {
+ case JPEG => ".jpeg"
+ case GIF => ".gif"
+ case PNG => ".png"
+ }
+
+ def contentType = this match {
+ case JPEG => "image/jpeg"
+ case GIF => "image/gif"
+ case PNG => "image/png"
+ }
+
+}
+
+
+object Image {
+
+ val supportedImages = Set(JPEG, GIF, PNG)
+
+ def apply(contentType: String): Option[Image] = {
+ contentType.trim.toLowerCase match {
+ case "image/jpeg" => Some(JPEG)
+ case "image/gif" => Some(GIF)
+ case "image/png" => Some(PNG)
+ case _ => None
+ }
+ }
+
+ def apply(cts: Iterable[String]): Option[Image] =
+ cts map Image.apply collectFirst {
+ case Some(lang) => lang
+ }
+}
+
+case object JPEG extends Image
+case object GIF extends Image
+case object PNG extends Image
--- a/src/main/scala/Lang.scala Wed Apr 11 14:56:25 2012 +0200
+++ b/src/main/scala/Lang.scala Fri Apr 13 17:43:15 2012 +0200
@@ -30,7 +30,6 @@
case HTML => "HTML"
case XHTML => "XHTML"
}
-
}
object Lang {
--- a/src/main/scala/ReadWriteWeb.scala Wed Apr 11 14:56:25 2012 +0200
+++ b/src/main/scala/ReadWriteWeb.scala Fri Apr 13 17:43:15 2012 +0200
@@ -1,6 +1,7 @@
package org.w3.readwriteweb
import auth.{AuthZ, NullAuthZ}
+import netty.ResponseBin
import org.w3.readwriteweb.util._
import scala.io.Source
@@ -85,6 +86,13 @@
val body = source.getLines.mkString("\n")
Ok ~> ViaSPARQL ~> ContentType("text/html") ~> ResponseString(body)
}
+ case GET(_) if representation.isInstanceOf[ImageRepr] => {
+ for (in <- r.getStream failMap {x => NotFound })
+ yield {
+ val ImageRepr(typ) = representation
+ Ok ~> ContentType(typ.contentType) ~> ResponseBin(in)
+ }
+ }
case GET(_) | HEAD(_) =>
for {
model <- r.get() failMap { x => NotFound }
@@ -101,10 +109,14 @@
}
res ~> ContentLocation( uri.toString ) // without this netty (perhaps jetty too?) sends very weird headers, breaking tests
}
+ case PUT(_) if representation.isInstanceOf[ImageRepr] => {
+ for (_ <- r.putStream(Body.stream(req)) failMap { t=> InternalServerError ~> ResponseString(t.getStackTraceString)})
+ yield Created
+ }
case PUT(_) & RequestLang(lang) if representation == DirectoryRepr => {
for {
bodyModel <- modelFromInputStream(Body.stream(req), uri, lang) failMap { t => BadRequest ~> ResponseString(t.getStackTraceString) }
- _ <- r.createDirectory(bodyModel) failMap { t => InternalServerError ~> ResponseString(t.getStackTraceString) }
+ _ <- r.createDirectory failMap { t => InternalServerError ~> ResponseString(t.getStackTraceString) }
} yield Created
}
case PUT(_) & RequestLang(lang) =>
@@ -190,9 +202,5 @@
}
res
}
-
-
-
-
}
--- a/src/main/scala/Representation.scala Wed Apr 11 14:56:25 2012 +0200
+++ b/src/main/scala/Representation.scala Fri Apr 13 17:43:15 2012 +0200
@@ -14,6 +14,9 @@
case "turtle" | "ttl" => RDFRepr(TURTLE)
case "rdf" => RDFRepr(RDFXML)
case "htm" | "html" | "xhtml" => HTMLRepr
+ case "jpeg" => ImageRepr(JPEG)
+ case "png" => ImageRepr(PNG)
+ case "gif" => ImageRepr(GIF)
case "/" => DirectoryRepr
case _ => UnknownRepr
}
@@ -21,16 +24,22 @@
val htmlContentTypes = Set("text/html", "application/xhtml+xml")
+ val imgageTypes = Set("image/jpeg","image/png","image/gif")
def acceptsHTML(ct: Iterable[String]) =
! (htmlContentTypes & ct.toSet).isEmpty
+
+ def acceptsPicture(ct: Iterable[String]) =
+ ! (imgageTypes & ct.toSet).isEmpty
def fromAcceptedContentTypes(ct: Iterable[String]): Representation = {
Lang(ct) map RDFRepr.apply getOrElse {
+ Image(ct).map(ImageRepr.apply(_)).getOrElse {
if (acceptsHTML(ct))
HTMLRepr
else
UnknownRepr
+ }
}
}
@@ -52,6 +61,7 @@
}
case class RDFRepr(lang: Lang) extends Representation
+case class ImageRepr(mime: Image) extends Representation
case object HTMLRepr extends Representation
case object DirectoryRepr extends Representation
case object UnknownRepr extends Representation
--- a/src/main/scala/Resource.scala Wed Apr 11 14:56:25 2012 +0200
+++ b/src/main/scala/Resource.scala Fri Apr 13 17:43:15 2012 +0200
@@ -6,6 +6,7 @@
import com.hp.hpl.jena.rdf.model._
import scalaz.{Resource => _, _}
import Scalaz._
+import java.io.InputStream
trait ResourceManager {
def basePath:String
@@ -19,12 +20,41 @@
trait Resource {
def name: URL
+
+ /**
+ * get the resource as a model.
+ * Note: the cache policy only really makes sense for remote resources, not for the file system.
+ * Note: returning a model only makes sense when the resource is something that can be transformed to
+ * one which is not always the case, especially not for images. (see below)
+ * @param policy
+ * @return
+ */
def get(policy: CacheControl.Value = CacheControl.CacheFirst): Validation[Throwable, Model]
+
+ /**
+ * Many resources are not images. We need to get inputStreams for this.
+ * (this model here is getting more and more complicated. The get that returns a model above cannot
+ * simply be reduced to this one, as in the FileResource doing a GET on the directory should return
+ * a Graph describing the directory for example. Dealing with this is going to be a bit more tricky than
+ * I (bblfish) have time for at this point - as it would probably require quite a deep rewrite.)
+ * )
+ * @return
+ */
+ def getStream: Validation[Throwable,InputStream]
def delete: Validation[Throwable, Unit]
def save(model:Model):Validation[Throwable, Unit]
+ /**
+ * PUT the inputstream in the location
+ *
+ * same comments as with getStream
+ *
+ * @param in inputstream containing serialisation
+ */
+ def putStream(in: InputStream): Validation[Throwable, Unit]
+
//These two methods only work when called on directories
- def createDirectory(model: Model): Validation[Throwable, Unit]
+ def createDirectory: Validation[Throwable, Unit]
def create(): Validation[Throwable, Resource]
}
--- a/src/main/scala/auth/WebIdClaim.scala Wed Apr 11 14:56:25 2012 +0200
+++ b/src/main/scala/auth/WebIdClaim.scala Fri Apr 13 17:43:15 2012 +0200
@@ -87,7 +87,7 @@
if (mod.getDatatype == XSDDatatype.XSDhexBinary &&
new BigInteger(stripSpace(mod.getLexicalForm), 16) == rsakey.getModulus) {
val exp = sol.getLiteral("e")
- numericDataTypes.contains(exp.getDatatype) && new BigInteger(exp.getLexicalForm) == rsakey.getPublicExponent
+ numericDataTypes.contains(exp.getDatatype) && new BigInteger(exp.getLexicalForm.trim) == rsakey.getPublicExponent
} else false
} catch {
case _ => false
--- a/src/main/scala/netty/ReadWriteWebNetty.scala Wed Apr 11 14:56:25 2012 +0200
+++ b/src/main/scala/netty/ReadWriteWebNetty.scala Fri Apr 13 17:43:15 2012 +0200
@@ -142,11 +142,9 @@
case class ResponseBin(bis: InputStream) extends ResponseStreamer {
override def stream(out: OutputStream) {
- var c=0
- val buf = new Array[Byte](1024)
- do {
- c = bis.read(buf)
- if (c > 0) out.write(buf,0,c)
- } while (c > -1)
+ val buf = new Array[Byte](4096)
+ Iterator continually bis.read(buf) takeWhile (-1 !=) foreach { bytesRead =>
+ out.write(buf,0,bytesRead)
+ }
}
}
\ No newline at end of file