--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/fis/fs/lib/AttachmentStore.scala Wed May 30 22:51:02 2012 +0200
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2010-2012 Tomas Zeman <tzeman@volny.cz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * (Originaly based on net.backup project, (c) of Tomas Zeman).
+ */
+package fis.fs.lib
+
+import java.io.InputStream
+import net.liftweb.common.{Box, Empty, Failure, Full, LazyLoggable}
+
+/**
+ * Generic interface contract for content retrieval/storage.
+ */
+trait AttachmentStore {
+
+ /**
+ * Stores content to repository.
+ * @param content Content stream.
+ * @return Returns content id and length.
+ */
+ def put(content: InputStream): Box[(String, Long)]
+
+ /**
+ * Reads content stream from repository.
+ * @param id Content id.
+ * @param block Function on input stream.
+ * @return T func result.
+ */
+ def get[T](id: String)(block: InputStream => T): Box[T]
+
+ /**
+ * Reads content stream from repository.
+ * Called must close the stream afterwards.
+ * @param id Content id.
+ * @return Content stream.
+ */
+ def getContent(id: String): Box[InputStream]
+
+ /**
+ * Removes content from repository.
+ * @param id Content id.
+ * @return True if content was successfuly removed.
+ */
+ def remove(id: String): Boolean
+
+ /**
+ * Checks if content <code>id</code> is in the repository.
+ * @param id Content id.
+ * @return True if content <code>id</code> is in the repository.
+ */
+ def exists(id: String): Boolean
+}
+
+import java.io.{BufferedInputStream, File, FileInputStream, FileOutputStream}
+import java.nio.channels.Channels
+import java.util.concurrent.atomic.AtomicLong
+import net.liftweb.util.Helpers._
+
+/**
+ * Filesystem based attachment store.
+ * @param baseDir Base directory. Attempts to create if does not exist.
+ */
+class FileSystemAttachmentStore(baseDir: File) extends AttachmentStore
+ with LazyLoggable {
+
+ /* C'tor */
+ if (!baseDir.exists) {
+ logger.info("Creating " + baseDir)
+ baseDir.mkdirs
+ }
+
+ if (baseDir.exists && !baseDir.isDirectory)
+ throw new IllegalArgumentException("baseDir %s must be a directory.".format(
+ baseDir.toString))
+
+ logger.info("Attachment store initialized in %s".format(baseDir.toString))
+
+ /* AttachmentStore implementation methods. */
+
+ override def put(content: InputStream): Box[(String, Long)] = for {
+ (tmp, os) <- tryo {
+ val f = File.createTempFile(".fscs.", ".tmp", baseDir)
+ val os = new FileOutputStream(f)
+ (f, os)
+ }
+ len <- use(os) {
+ os getChannel() transferFrom(Channels newChannel content, 0, Long.MaxValue)
+ }
+ (id, f, renamed) <- tryo {
+ val s = serial.incrementAndGet.toString
+ val f = new File(baseDir, s)
+ (s, f, tmp renameTo f)
+ } if renamed
+ } yield (id, len)
+
+ def getContent(id: String): Box[InputStream] = withFile(id) { f =>
+ new BufferedInputStream(new FileInputStream(f)) }
+
+ def get[T](id: String)(block: InputStream => T): Box[T] =
+ withStream(id)(block)
+
+ override def remove(id: String): Boolean =
+ withFile(id)(_.delete) openOr false
+
+ override def exists(id: String): Boolean =
+ withFile(id)(_.exists) openOr false
+
+ /* Implementation private. */
+
+ def use[T](c: { def close(): Unit })(block: => T): Box[T] = {
+ val r = tryo(block)
+ tryo(c.close)
+ r
+ }
+
+ protected def withFile[T](id: String)(block: File => T): Box[T] = for {
+ f <- getFile(id)
+ r <- tryo(block(f))
+ } yield r
+
+ protected def withStream[T](id: String)(block: InputStream => T): Box[T] = (for {
+ f <- getFile(id)
+ is <- tryo { new BufferedInputStream(new FileInputStream(f)) }
+ } yield {
+ logger.debug("withStream(%s)".format(id))
+ val r = tryo(block(is))
+ tryo { is.close }
+ r
+ }) flatMap { v => v }
+
+ protected def sanitizeId(id: String): Box[String] = for {
+ i <- (Box !! id) if !i.exists(!_.isDigit)
+ } yield i
+
+ protected def getFile(id: String): Box[File] = for {
+ i <- sanitizeId(id)
+ } yield new File(baseDir, i)
+
+ private lazy val serial = new AtomicLong(millis)
+}
+
+
+// vim: set ts=2 sw=2 et: