package sbt
import java.io.{File,FileOutputStream}
import java.util.concurrent.Callable
class ComponentManager(globalLock: xsbti.GlobalLock, provider: xsbti.ComponentProvider, ivyHome: Option[File], val log: Logger)
{
private[this] val ivyCache = new IvyCache(ivyHome)
def files(id: String)(ifMissing: IfMissing): Iterable[File] =
{
def fromGlobal =
lockGlobalCache {
try { update(id); getOrElse(createAndCache) }
catch { case e: NotInCache => createAndCache }
}
def getOrElse(orElse: => Iterable[File]): Iterable[File] =
{
val existing = provider.component(id)
if(existing.isEmpty) orElse else existing
}
def notFound = invalid("Could not find required component '" + id + "'")
def createAndCache =
ifMissing match {
case IfMissing.Fail => notFound
case d: IfMissing.Define =>
d()
if(d.cache) cache(id)
getOrElse(notFound)
}
lockLocalCache { getOrElse(fromGlobal) }
}
private def lockLocalCache[T](action: => T): T = lock(provider.lockFile)( action )
private def lockGlobalCache[T](action: => T): T = lock(ivyCache.lockFile)( action )
private def lock[T](file: File)(action: => T): T = globalLock(file, new Callable[T] { def call = action })
def file(id: String)(ifMissing: IfMissing): File =
files(id)(ifMissing).toList match {
case x :: Nil => x
case xs => invalid("Expected single file for component '" + id + "', found: " + xs.mkString(", "))
}
private def invalid(msg: String) = throw new InvalidComponent(msg)
private def invalid(e: NotInCache) = throw new InvalidComponent(e.getMessage, e)
def define(id: String, files: Iterable[File]) = lockLocalCache { provider.defineComponent(id, files.toSeq.toArray) }
private def update(id: String): Unit = ivyCache.withCachedJar(sbtModuleID(id), Some(globalLock), log)(jar => define(id, Seq(jar)) )
private def sbtModuleID(id: String) = ModuleID(SbtArtifacts.Organization, id, ComponentManager.stampedVersion)
def cache(id: String): Unit = ivyCache.cacheJar(sbtModuleID(id), file(id)(IfMissing.Fail), Some(globalLock), log)
def clearCache(id: String): Unit = lockGlobalCache { ivyCache.clearCachedJar(sbtModuleID(id), Some(globalLock), log) }
}
class InvalidComponent(msg: String, cause: Throwable) extends RuntimeException(msg, cause)
{
def this(msg: String) = this(msg, null)
}
sealed trait IfMissing extends NotNull
object IfMissing
{
object Fail extends IfMissing
final class Define(val cache: Boolean, define: => Unit) extends IfMissing { def apply() = define }
}
object ComponentManager
{
lazy val (version, timestamp) =
{
val properties = new java.util.Properties
val propertiesStream = versionResource.openStream
try { properties.load(propertiesStream) } finally { propertiesStream.close() }
(properties.getProperty("version"), properties.getProperty("timestamp"))
}
lazy val stampedVersion = version + "_" + timestamp
import java.net.URL
private def versionResource: URL = getClass.getResource("/xsbt.version.properties")
}