diff -r 49eb72a46208 -r 1fcbeae1f9da src/main/scala/fis/fs/lib/AttachmentStore.scala --- /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 + * + * 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 id is in the repository. + * @param id Content id. + * @return True if content id 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: