src/main/scala/fis/fs/lib/AttachmentStore.scala
changeset 100 1fcbeae1f9da
--- /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: