blob: 8487d3d691dc88d120e5a98c3309970d7ee1eae6 [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.get(segments(path)).isDefined
override def isDirectory(path: String) =
root.get(segments(path)) match {
case Some(obj) => obj.isDirectory
case _ => false
}
override def size(path: String) =
root.get(segments(path)) match {
case Some(obj) => obj.size
case _ => 0L
}
override def lastModified(path: String) =
root.get(segments(path)) match {
case Some(obj) => obj.lastModified
case _ => 0L
}
override def list(path: String) =
root.get(segments(path)) match {
case Some(obj: VirtualDirectory) => obj.list()
case _ => None
}
override def rename(from: String, to: String) =
if (from != "" && exists(from) && !exists(to)) {
val segmentsTo = segments(to)
root.get(segmentsTo.dropRight(1)) match {
case Some(toParent: VirtualDirectory) =>
val toName = segmentsTo.last
val segmentsFrom = segments(from)
val fromParent = root.get(segmentsFrom.dropRight(1)).get.asInstanceOf[VirtualDirectory]
val fromName = segmentsFrom.last
val obj = fromParent.children(fromName)
fromParent.children -= fromName
fromParent.lastModified = System.currentTimeMillis()
toParent.children += toName -> obj
toParent.lastModified = System.currentTimeMillis()
obj.lastModified = System.currentTimeMillis()
true
case _ => false
}
}
else false
override def setLastModified(path: String, time: Long) =
root.get(segments(path)) match {
case Some(obj) if time >= 0 =>
obj.lastModified = time
true
case _ => false
}
// ----------------------------------------------------------------------- //
override protected def makeDirectory(path: String) = {
val parts = segments(path)
if (parts.isEmpty) false
else {
root.get(parts.dropRight(1)) match {
case Some(parent: VirtualDirectory) => parent.makeDirectory(parts.last)
case _ => false
}
}
}
override protected def delete(path: String) = {
val parts = segments(path)
if (parts.isEmpty) false
else {
root.get(parts.dropRight(1)) match {
case Some(parent: VirtualDirectory) => parent.delete(parts.last)
case _ => false
}
}
}
// ----------------------------------------------------------------------- //
override def close() = {
super.close()
root.children.clear()
}
// ----------------------------------------------------------------------- //
override protected def openInputStream(path: String) =
root.get(segments(path)) match {
case Some(obj: VirtualFile) => obj.openInputStream()
case _ => None
}
override protected def openOutputStream(path: String, mode: Mode.Value) = {
val parts = segments(path)
if (parts.isEmpty) None
else {
root.get(parts.dropRight(1)) match {
case Some(parent: VirtualDirectory) => parent.openOutputStream(parts.last, mode)
case _ => None
}
}
}
// ----------------------------------------------------------------------- //
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 isDirectory: Boolean
def size: Long
var lastModified = System.currentTimeMillis()
def load(nbt: NBTTagCompound) {
if (nbt.hasKey("lastModified"))
lastModified = nbt.getLong("lastModified")
}
def save(nbt: NBTTagCompound) {
nbt.setLong("lastModified", lastModified)
}
def get(path: Iterable[String]): Option[VirtualObject] =
if (path.isEmpty) Some(this) else None
def canDelete: Boolean
}
// ----------------------------------------------------------------------- //
private class VirtualFile extends VirtualObject {
val data = mutable.ArrayBuffer.empty[Byte]
var stream: Option[VirtualFileOutputStream] = None
override def isDirectory = false
override def size = data.length
def openInputStream() = Some(new VirtualFileInputStream(this))
def openOutputStream(mode: Mode.Value) =
if (stream.isDefined) None
else {
if (mode == Mode.Write) {
data.clear()
lastModified = System.currentTimeMillis()
}
stream = Some(new VirtualFileOutputStream(this))
stream
}
override def load(nbt: NBTTagCompound) {
super.load(nbt)
data.clear()
data ++= nbt.getByteArray("data")
}
override def save(nbt: NBTTagCompound) {
super.save(nbt)
nbt.setByteArray("data", data.toArray)
}
override def canDelete = stream.isEmpty
}
// ----------------------------------------------------------------------- //
private class VirtualDirectory extends VirtualObject {
val children = mutable.Map.empty[String, VirtualObject]
override def isDirectory = true
override def size = 0
def list() = Some(children.map {
case (childName, child) => if (child.isDirectory) childName + "/" else childName
}.toArray)
def makeDirectory(name: String) =
if (children.contains(name)) false
else {
children += name -> new VirtualDirectory
lastModified = System.currentTimeMillis()
true
}
def delete(name: String) = {
children.get(name) match {
case Some(child) if child.canDelete =>
children -= name
lastModified = System.currentTimeMillis()
true
case _ => false
}
}
def openOutputStream(name: String, mode: Mode.Value) =
children.get(name) match {
case Some(obj: VirtualFile) => obj.openOutputStream(mode)
case None =>
val child = new VirtualFile
children += name -> child
lastModified = System.currentTimeMillis()
child.openOutputStream(mode)
case _ => None
}
override def load(nbt: NBTTagCompound) {
super.load(nbt)
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) {
super.save(nbt)
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
}
}
override def canDelete = children.isEmpty
}
// ----------------------------------------------------------------------- //
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
file.data.view(position, file.data.length).copyToArray(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.OutputStream {
private var isClosed = false
override def close() = if (!isClosed) {
isClosed = true
file.stream = None
}
override def write(b: Array[Byte], off: Int, len: Int) =
if (!isClosed) {
file.data ++= b.view(off, off + len)
file.lastModified = System.currentTimeMillis()
}
else throw new io.IOException("file is closed")
override def write(b: Int) =
if (!isClosed) {
file.data += b.toByte
file.lastModified = System.currentTimeMillis()
}
else throw new io.IOException("file is closed")
}
}