blob: 14aa5277e0ee402ddd12db66b74a9a177f67516e [file] [log] [blame] [raw]
package li.cil.oc.server.fs
import java.io
import java.io.ByteArrayInputStream
import java.util.concurrent.Callable
import java.util.zip.{ZipEntry, ZipFile}
import com.google.common.cache.CacheBuilder
import li.cil.oc.OpenComputers
import li.cil.oc.server.fs.ZipFileInputStreamFileSystem.{ArchiveDirectory, ArchiveFile}
import org.apache.logging.log4j.Level
import scala.collection.mutable
import scala.language.postfixOps
class ZipFileInputStreamFileSystem(private val archive: ArchiveDirectory) extends InputStreamFileSystem {
def spaceTotal = spaceUsed
def spaceUsed = spaceUsed_
private lazy val spaceUsed_ = ZipFileInputStreamFileSystem.synchronized {
def recurse(d: ArchiveDirectory): Long = d.children.foldLeft(0L)((acc, c) => acc + (c match {
case directory: ArchiveDirectory => recurse(directory)
case file: ArchiveFile => file.size
}))
recurse(archive)
}
// ----------------------------------------------------------------------- //
override def exists(path: String) = ZipFileInputStreamFileSystem.synchronized {
entry(path).isDefined
}
override def size(path: String) = ZipFileInputStreamFileSystem.synchronized {
entry(path) match {
case Some(file) if !file.isDirectory => file.size
case _ => 0L
}
}
override def isDirectory(path: String) = ZipFileInputStreamFileSystem.synchronized {
entry(path).exists(_.isDirectory)
}
def lastModified(path: String) = ZipFileInputStreamFileSystem.synchronized {
entry(path) match {
case Some(file) => file.lastModified
case _ => 0L
}
}
override def list(path: String) = ZipFileInputStreamFileSystem.synchronized {
entry(path) match {
case Some(entry) if entry.isDirectory => entry.list()
case _ => null
}
}
// ----------------------------------------------------------------------- //
override protected def openInputChannel(path: String) = ZipFileInputStreamFileSystem.synchronized {
entry(path).map(entry => new InputStreamChannel(entry.openStream()))
}
// ----------------------------------------------------------------------- //
private def entry(path: String) = {
val cleanPath = "/" + path.replace("\\", "/").replace("//", "/").stripPrefix("/").stripSuffix("/")
if (cleanPath == "/") Some(archive)
else archive.find(cleanPath.split("/"))
}
}
object ZipFileInputStreamFileSystem {
private val cache = com.google.common.cache.CacheBuilder.newBuilder().
weakValues().
asInstanceOf[CacheBuilder[String, ArchiveDirectory]].
build[String, ArchiveDirectory]()
def fromFile(file: io.File, innerPath: String) = ZipFileInputStreamFileSystem.synchronized {
try {
Option(cache.get(file.getPath + ":" + innerPath, new Callable[ArchiveDirectory] {
def call = {
val zip = new ZipFile(file.getPath)
try {
val cleanedPath = innerPath.stripPrefix("/").stripSuffix("/") + "/"
val rootEntry = zip.getEntry(cleanedPath)
if (rootEntry == null || !rootEntry.isDirectory) {
throw new IllegalArgumentException(s"Root path $innerPath doesn't exist or is not a directory in ZIP file ${file.getName}.")
}
val directories = mutable.Set.empty[ArchiveDirectory]
val files = mutable.Set.empty[ArchiveFile]
val iterator = zip.entries()
while (iterator.hasMoreElements) {
val entry = iterator.nextElement()
if (entry.getName.startsWith(cleanedPath)) {
if (entry.isDirectory) directories += new ArchiveDirectory(entry, cleanedPath)
else files += new ArchiveFile(zip, entry, cleanedPath)
}
}
var root: ArchiveDirectory = null
for (entry <- directories ++ files) {
if (entry.path.length > 0) {
val parent = entry.path.substring(0, math.max(entry.path.lastIndexOf('/'), 0))
directories.find(d => d.path == parent) match {
case Some(directory) => directory.children += entry
case _ =>
}
}
else {
assert(entry.isInstanceOf[ArchiveDirectory])
root = entry.asInstanceOf[ArchiveDirectory]
}
}
root
}
finally {
zip.close()
}
}
})) match {
case Some(archive) => new ZipFileInputStreamFileSystem(archive)
case _ => null
}
}
catch {
case e: Throwable =>
OpenComputers.log.log(Level.WARN, "Failed creating ZIP file system.", e)
null
}
}
abstract class Archive(entry: ZipEntry, root: String) {
val path = entry.getName.stripPrefix(root).stripSuffix("/")
val name = path.substring(path.lastIndexOf('/') + 1)
val lastModified = entry.getTime
val isDirectory = entry.isDirectory
def size: Int
def list(): Array[String]
def openStream(): io.InputStream
def find(path: Iterable[String]): Option[Archive]
}
private class ArchiveFile(zip: ZipFile, entry: ZipEntry, root: String) extends Archive(entry, root) {
val data = {
val in = zip.getInputStream(entry)
Iterator.continually(in.read).takeWhile(-1 !=).map(_.toByte).toArray
}
val size = data.length
def list() = null
def openStream() = new ByteArrayInputStream(data)
def find(path: Iterable[String]) =
if (path.size == 1 && path.head == name) Some(this)
else None
}
private class ArchiveDirectory(entry: ZipEntry, root: String) extends Archive(entry, root) {
val children = mutable.Set.empty[Archive]
val size = 0
def list() = children.map(c => c.name + (if (c.isDirectory) "/" else "")).toArray
def openStream() = null
def find(path: Iterable[String]) =
if (path.head == name) {
if (path.size == 1) Some(this)
else {
val subPath = path.drop(1)
children.map(_.find(subPath)).collectFirst {
case Some(a) => a
}
}
}
else None
}
}