/*
* 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: