blob: 93152cb01c284a8f11ee7fbd6b5fd2ba634508d9 [file] [log] [blame] [raw]
package li.cil.oc.server.fs
import java.io.FileNotFoundException
import java.io.IOException
import java.nio.ByteBuffer
import java.nio.channels.ReadableByteChannel
import li.cil.oc.api
import li.cil.oc.api.fs.Mode
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.nbt.NBTTagList
import net.minecraftforge.common.util.Constants.NBT
import scala.collection.mutable
trait InputStreamFileSystem extends api.fs.FileSystem {
private val handles = mutable.Map.empty[Int, Handle]
// ----------------------------------------------------------------------- //
override def isReadOnly = true
override def delete(path: String) = false
override def makeDirectory(path: String) = false
override def rename(from: String, to: String) = false
override def setLastModified(path: String, time: Long) = false
// ----------------------------------------------------------------------- //
override def open(path: String, mode: Mode) = {
FileSystem.validatePath(path)
this.synchronized(if (mode == Mode.Read && exists(path) && !isDirectory(path)) {
val handle = Iterator.continually((Math.random() * Int.MaxValue).toInt + 1).filterNot(handles.contains).next()
openInputChannel(path) match {
case Some(channel) =>
handles += handle -> new Handle(this, handle, path, channel)
handle
case _ => throw new FileNotFoundException(path)
}
} else throw new FileNotFoundException(path))
}
override def getHandle(handle: Int): api.fs.Handle = this.synchronized(handles.get(handle).orNull)
override def close() = this.synchronized {
for (handle <- handles.values)
handle.close()
handles.clear()
}
// ----------------------------------------------------------------------- //
override def load(nbt: NBTTagCompound) {
val handlesNbt = nbt.getTagList("input", NBT.TAG_COMPOUND)
(0 until handlesNbt.tagCount).map(handlesNbt.getCompoundTagAt).foreach(handleNbt => {
val handle = handleNbt.getInteger("handle")
val path = handleNbt.getString("path")
val position = handleNbt.getLong("position")
openInputChannel(path) match {
case Some(channel) =>
val fileHandle = new Handle(this, handle, path, channel)
channel.position(position)
handles += handle -> fileHandle
case _ => // The source file seems to have disappeared since last time.
}
})
}
override def save(nbt: NBTTagCompound) = this.synchronized {
val handlesNbt = new NBTTagList()
for (file <- handles.values) {
assert(file.channel.isOpen)
val handleNbt = new NBTTagCompound()
handleNbt.setInteger("handle", file.handle)
handleNbt.setString("path", file.path)
handleNbt.setLong("position", file.position)
handlesNbt.appendTag(handleNbt)
}
nbt.setTag("input", handlesNbt)
}
// ----------------------------------------------------------------------- //
protected def openInputChannel(path: String): Option[InputChannel]
protected trait InputChannel extends ReadableByteChannel {
def isOpen: Boolean
def close()
def position: Long
def position(newPosition: Long): Long
def read(dst: Array[Byte]): Int
override def read(dst: ByteBuffer) = {
if (dst.hasArray) {
read(dst.array())
}
else {
val count = math.max(0, dst.limit - dst.position)
val buffer = new Array[Byte](count)
val n = read(buffer)
if (n > 0) dst.put(buffer, 0, n)
n
}
}
}
protected class InputStreamChannel(val inputStream: java.io.InputStream) extends InputChannel {
var isOpen = true
private var position_ = 0L
override def close() = if (isOpen) {
isOpen = false
inputStream.close()
}
override def position = position_
override def position(newPosition: Long) = {
inputStream.reset()
position_ = inputStream.skip(newPosition)
position_
}
override def read(dst: Array[Byte]) = {
val read = inputStream.read(dst)
position_ += read
read
}
}
// ----------------------------------------------------------------------- //
private class Handle(val owner: InputStreamFileSystem, val handle: Int, val path: String, val channel: InputChannel) extends api.fs.Handle {
override def position = channel.position
override def length = owner.size(path)
override def close() = if (channel.isOpen) {
owner.handles -= handle
channel.close()
}
override def read(into: Array[Byte]) = channel.read(into)
override def seek(to: Long) = channel.position(to)
override def write(value: Array[Byte]) = throw new IOException("bad file descriptor")
}
}