blob: ec29ff19184be5749fd83ec20c28a4f015835d36 [file] [log] [blame] [raw]
package li.cil.oc.server.fs
import java.io
import li.cil.oc.api.fs.Mode
import net.minecraft.nbt.{NBTTagList, NBTTagCompound}
import scala.collection.mutable
class VirtualFileSystem extends OutputStreamFileSystem {
private val root = new VirtualDirectory
// ----------------------------------------------------------------------- //
override def exists(path: String) =
root.exists(segments(path))
override def isDirectory(path: String) =
root.isDirectory(segments(path))
override def size(path: String) =
root.size(segments(path))
override def list(path: String) =
root.list(segments(path))
override def rename(from: String, to: String) =
if (from != "" && exists(from) && !exists(to)) {
root.get(segments(to).dropRight(1)) match {
case Some(toParent: VirtualDirectory) =>
val fromParent = root.get(segments(from).dropRight(1)).get.asInstanceOf[VirtualDirectory]
val fromName = segments(from).last
val toName = segments(to).last
val obj = fromParent.children(fromName)
fromParent.children -= fromName
toParent.children += toName -> obj
true
case _ => false
}
}
else false
// ----------------------------------------------------------------------- //
override protected def makeDirectory(path: String) =
root.makeDirectory(segments(path))
override protected def delete(path: String) =
root.delete(segments(path))
// ----------------------------------------------------------------------- //
override def close() = {
super.close()
root.children.clear()
}
// ----------------------------------------------------------------------- //
override protected def openInputStream(path: String) =
root.openInputStream(segments(path))
override protected def openOutputStream(path: String, mode: Mode.Value) =
root.openOutputStream(segments(path), mode)
// ----------------------------------------------------------------------- //
override def load(nbt: NBTTagCompound) = {
root.load(nbt)
super.load(nbt) // Last to ensure streams can be re-opened.
}
override def save(nbt: NBTTagCompound) = {
super.save(nbt) // First to allow flushing.
root.save(nbt)
}
// ----------------------------------------------------------------------- //
private def segments(path: String) = path.split("/").view.filter(_ != "")
// ----------------------------------------------------------------------- //
private abstract class VirtualObject {
def exists(path: Iterable[String]) = path.isEmpty
def isDirectory(path: Iterable[String] = Iterable.empty[String]): Boolean
def size(path: Iterable[String]): Long
def list(path: Iterable[String]): Option[Array[String]]
def makeDirectory(path: Iterable[String]): Boolean
def delete(path: Iterable[String]): Boolean
def canDelete: Boolean
def openInputStream(path: Iterable[String]): Option[io.InputStream]
def openOutputStream(path: Iterable[String], mode: Mode.Value): Option[io.OutputStream]
def load(nbt: NBTTagCompound)
def save(nbt: NBTTagCompound)
def get(path: Iterable[String]): Option[VirtualObject] = if (path.isEmpty) Some(this) else None
}
private class VirtualFile extends VirtualObject {
var data = Array.empty[Byte]
var stream: Option[VirtualFileOutputStream] = None
override def isDirectory(path: Iterable[String]) = false
override def size(path: Iterable[String]) = data.length
override def list(path: Iterable[String]) = None
override def makeDirectory(path: Iterable[String]) = false
override def delete(path: Iterable[String]) = false
override def canDelete = stream.isEmpty
override def openInputStream(path: Iterable[String]) =
if (path.isEmpty) Some(new VirtualFileInputStream(this))
else None
override def openOutputStream(path: Iterable[String], mode: Mode.Value) =
if (path.isEmpty) {
if (stream.isDefined) throw new io.FileNotFoundException()
if (mode == Mode.Write)
data = Array.empty[Byte]
stream = Some(new VirtualFileOutputStream(this))
stream
}
else None
override def load(nbt: NBTTagCompound) {
data = nbt.getByteArray("data")
}
override def save(nbt: NBTTagCompound) {
nbt.setByteArray("data", data)
}
}
private class VirtualDirectory extends VirtualObject {
val children = mutable.Map.empty[String, VirtualObject]
override def exists(path: Iterable[String]) = super.exists(path) || (children.get(path.head) match {
case Some(child) => child.exists(path.drop(1))
case _ => false
})
override def isDirectory(path: Iterable[String]) = path.isEmpty || (children.get(path.head) match {
case Some(child) => child.isDirectory(path.drop(1))
case _ => false
})
override def size(path: Iterable[String]) =
if (path.isEmpty) 0
else children.get(path.head) match {
case Some(child) => child.size(path.drop(1))
case _ => 0
}
override def list(path: Iterable[String]) =
if (path.isEmpty) Some(children.map {
case (childName, child) => if (child.isDirectory()) childName + "/" else childName
}.toArray)
else children.get(path.head) match {
case Some(child) => child.list(path.drop(1))
case _ => None
}
override def makeDirectory(path: Iterable[String]) =
if (path.size == 1) {
val directory = path.head
if (children.contains(directory)) false
else {
children += directory -> new VirtualDirectory
true
}
} else children.get(path.head) match {
case Some(child) => child.makeDirectory(path.drop(1))
case _ => false
}
override def delete(path: Iterable[String]) =
if (path.size == 1) {
val childName = path.head
children.get(childName) match {
case Some(child) if child.canDelete =>
children -= childName
true
case _ =>
false
}
} else children.get(path.head) match {
case Some(child) => child.delete(path.drop(1))
case _ => false
}
override def canDelete = children.isEmpty
override def openInputStream(path: Iterable[String]) =
if (path.isEmpty) None
else children.get(path.head) match {
case Some(child) => child.openInputStream(path.drop(1))
case _ => None
}
override def openOutputStream(path: Iterable[String], mode: Mode.Value) =
if (path.isEmpty) None
else children.get(path.head) match {
case Some(child) => child.openOutputStream(path.drop(1), mode)
case None if path.size == 1 =>
val childName = path.head
val child = new VirtualFile
children += childName -> child
child.openOutputStream(Array.empty[String], mode)
case _ => None
}
override def load(nbt: NBTTagCompound) {
val childrenNbt = nbt.getTagList("children")
(0 until childrenNbt.tagCount).map(childrenNbt.tagAt).map(_.asInstanceOf[NBTTagCompound]).foreach(childNbt => {
val child =
if (childNbt.getBoolean("isDirectory")) new VirtualDirectory
else new VirtualFile
child.load(childNbt)
children += childNbt.getString("name") -> child
})
}
override def save(nbt: NBTTagCompound) {
val childrenNbt = new NBTTagList()
for ((childName, child) <- children) {
val childNbt = new NBTTagCompound()
childNbt.setBoolean("isDirectory", child.isDirectory())
childNbt.setString("name", childName)
child.save(childNbt)
childrenNbt.appendTag(childNbt)
}
nbt.setTag("children", childrenNbt)
}
override def get(path: Iterable[String]) =
super.get(path) orElse {
children.get(path.head) match {
case Some(child) => child.get(path.drop(1))
case _ => None
}
}
}
// ----------------------------------------------------------------------- //
private class VirtualFileInputStream(val file: VirtualFile) extends io.InputStream {
private var isClosed = false
private var position = 0
override def available() =
if (isClosed) 0
else (file.data.length - position) max 0
override def close() = isClosed = true
def read() =
if (!isClosed) {
if (available == 0) -1
else {
position += 1
file.data(position - 1)
}
}
else throw new io.IOException("file is closed")
override def read(b: Array[Byte], off: Int, len: Int) =
if (!isClosed) {
if (available == 0) -1
else {
val n = len min available
Array.copy(file.data, position, b, off, n)
position += n
n
}
}
else throw new io.IOException("file is closed")
override def reset() =
if (!isClosed) {
position = 0
}
else throw new io.IOException("file is closed")
override def skip(n: Long) =
if (!isClosed) {
position = ((position + n) min Int.MaxValue).toInt
position
}
else throw new io.IOException("file is closed")
}
private class VirtualFileOutputStream(val file: VirtualFile) extends io.ByteArrayOutputStream {
private var isClosed = false
override def close() = if (!isClosed) {
flush()
isClosed = true
file.stream = None
}
override def flush() =
if (!isClosed) {
file.data ++= toByteArray
reset()
} else throw new io.IOException("file is closed")
override def write(b: Array[Byte], off: Int, len: Int) =
if (!isClosed) super.write(b, off, len)
else throw new io.IOException("file is closed")
override def write(b: Int) =
if (!isClosed) super.write(b)
else throw new io.IOException("file is closed")
}
}