blob: e01392f00b74c207a95445b8735e4eb203228005 [file] [log] [blame] [raw]
package li.cil.oc.server.component
import java.io.{FileNotFoundException, IOException}
import li.cil.oc.api
import li.cil.oc.api.Network
import li.cil.oc.api.fs.Mode
import li.cil.oc.api.network.Message
import net.minecraft.nbt.NBTTagCompound
import scala.collection.mutable
class FileSystem(val fileSystem: api.FileSystem) extends ItemComponent {
private val handles = mutable.Map.empty[String, mutable.Set[Int]]
override def name = "fs"
override def receive(message: Message) = {
message.data match {
case Array() if message.name == "network.disconnect" && handles.contains(message.source.address.get) =>
for (handle <- handles(message.source.address.get)) {
fileSystem.file(handle) match {
case None => // Maybe file system was accessed from somewhere else.
case Some(file) => file.close()
}
}
// Actually only neighbor computers can have handles, but this way it's
// nice and separate from the actual fs functionality.
case Array() if message.name == "computer.stopped" =>
handles.get(message.source.address.get) match {
case None => // Computer had no open files.
case Some(set) =>
set.foreach(handle => fileSystem.file(handle) match {
case None => // Invalid handle... huh.
case Some(file) => file.close()
})
set.clear()
}
None
case _ => // Ignore.
}
super.receive(message)
}
override protected def receiveFromNeighbor(network: Network, message: Message) =
try {
message.data match {
case Array(path: Array[Byte]) if message.name == "fs.exists" =>
Some(Array(fileSystem.exists(clean(path)).asInstanceOf[Any]))
case Array(path: Array[Byte]) if message.name == "fs.exists" =>
Some(Array(fileSystem.size(clean(path)).asInstanceOf[Any]))
case Array(path: Array[Byte]) if message.name == "fs.isDirectory" =>
Some(Array(fileSystem.isDirectory(clean(path)).asInstanceOf[Any]))
case Array(path: Array[Byte]) if message.name == "fs.list" =>
fileSystem.list(clean(path)) match {
case Some(list) => Some(list.map(_.asInstanceOf[Any]))
case _ => None
}
case Array(path: Array[Byte]) if message.name == "fs.remove" =>
Some(Array(fileSystem.remove(clean(path)).asInstanceOf[Any]))
case Array(from: Array[Byte], to: Array[Byte]) if message.name == "fs.rename" =>
Some(Array(fileSystem.rename(clean(from), clean(to)).asInstanceOf[Any]))
case Array(path: Array[Byte], mode: Array[Byte]) if message.name == "fs.open" =>
val handle = fileSystem.open(clean(path), Mode.parse(new String(mode, "UTF-8")))
if (handle > 0) {
handles.getOrElseUpdate(message.source.address.get, mutable.Set.empty[Int]) += handle
}
Some(Array(handle.asInstanceOf[Any]))
case Array(handle: Double) if message.name == "fs.close" =>
fileSystem.file(handle.toInt) match {
case None => // Ignore.
case Some(file) =>
handles.get(message.source.address.get) match {
case None => // Not the owner of this handle.
case Some(set) => if (set.remove(handle.toInt)) file.close()
}
}
None
case Array(handle: Double, n: Double) if message.name == "fs.read" && n > 0 =>
fileSystem.file(handle.toInt) match {
case None => None
case Some(file) =>
// Limit reading to chunks of 8KB to avoid crazy allocations.
val buffer = new Array[Byte](n.toInt min (8 * 1024))
val read = file.read(buffer)
if (read >= 0) {
val result = new Array[Byte](read)
Array.copy(buffer, 0, result, 0, read)
Some(Array(result))
}
else {
Some(Array(Unit))
}
}
case Array(handle: Double, whence: Array[Byte], offset: Double) if message.name == "fs.seek" =>
fileSystem.file(handle.toInt) match {
case None => None
case Some(file) =>
if (offset.toInt != 0) new String(whence, "UTF-8") match {
case "cur" => file.seek(file.position + offset.toInt)
case "set" => file.seek(offset.toLong)
case "end" => file.seek(file.length + offset.toInt)
case _ => throw new IllegalArgumentException("offset out of range")
}
Some(Array(file.position.asInstanceOf[Any]))
}
case Array(handle: Double, value: Array[Byte]) if message.name == "fs.write" =>
fileSystem.file(handle.toInt) match {
case None => None
case Some(file) => file.write(value); Some(Array(true.asInstanceOf[Any]))
}
case _ => None
}
} catch {
case e@(_: IOException | _: IllegalArgumentException) if e.getMessage != null && !e.getMessage.isEmpty =>
Some(Array(Unit, e.getMessage))
case e: FileNotFoundException =>
Some(Array(Unit, "file not found"))
case _: IllegalArgumentException =>
Some(Array(Unit, "invalid argument"))
case e: IOException =>
Some(Array(Unit, e.toString))
}
private def clean(path: Array[Byte]) = {
val result = com.google.common.io.Files.simplifyPath(new String(path, "UTF-8"))
if (result.startsWith("../")) throw new FileNotFoundException()
if (result == "/" || result == ".") ""
else result
}
override protected def onDisconnect() {
super.onDisconnect()
fileSystem.close()
}
override def load(nbt: NBTTagCompound) {
super.load(nbt)
fileSystem.load(nbt.getCompoundTag("fs"))
}
override def save(nbt: NBTTagCompound) {
super.save(nbt)
val fsNbt = new NBTTagCompound()
fileSystem.save(fsNbt)
nbt.setCompoundTag("fs", fsNbt)
}
}