Made files opened in read mode seekable more reliably.

This commit is contained in:
Florian Nücke 2014-04-20 22:12:00 +02:00
parent bbe80bc5de
commit d0d4f502ba
7 changed files with 69 additions and 34 deletions

View File

@ -94,7 +94,7 @@ trait Buffered extends OutputStreamFileSystem {
FileUtils.deleteQuietly(childFile) FileUtils.deleteQuietly(childFile)
childFile.createNewFile() childFile.createNewFile()
val out = new io.FileOutputStream(childFile).getChannel val out = new io.FileOutputStream(childFile).getChannel
val in = java.nio.channels.Channels.newChannel(openInputStream(childPath).get) val in = openInputChannel(childPath).get
out.transferFrom(in, 0, Long.MaxValue) out.transferFrom(in, 0, Long.MaxValue)
out.close() out.close()
in.close() in.close()

View File

@ -25,8 +25,8 @@ class CC15FileSystem(val mount: IMount) extends InputStreamFileSystem {
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
protected def openInputStream(path: String) = try { protected def openInputChannel(path: String) = try {
Some(mount.openForRead(path)) Some(new InputFileChannel(mount.openForRead(path)))
} catch { } catch {
case _: Throwable => None case _: Throwable => None
} }

View File

@ -25,8 +25,8 @@ class CC16FileSystem(val mount: IMount) extends InputStreamFileSystem {
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
protected def openInputStream(path: String) = try { protected def openInputChannel(path: String) = try {
Some(mount.openForRead(path)) Some(new InputFileChannel(mount.openForRead(path)))
} catch { } catch {
case _: Throwable => None case _: Throwable => None
} }

View File

@ -1,6 +1,7 @@
package li.cil.oc.server.fs package li.cil.oc.server.fs
import java.io import java.io
import java.nio.channels.SeekableByteChannel
trait FileInputStreamFileSystem extends InputStreamFileSystem { trait FileInputStreamFileSystem extends InputStreamFileSystem {
protected val root: io.File protected val root: io.File
@ -43,6 +44,6 @@ trait FileInputStreamFileSystem extends InputStreamFileSystem {
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
override protected def openInputStream(path: String): Option[io.InputStream] = override protected def openInputChannel(path: String): Option[SeekableByteChannel] =
Some(new io.FileInputStream(new io.File(root, path))) Some(new io.RandomAccessFile(new io.File(root, path), "r").getChannel)
} }

View File

@ -1,10 +1,12 @@
package li.cil.oc.server.fs package li.cil.oc.server.fs
import java.io.{FileNotFoundException, IOException, InputStream} import java.io.{FileNotFoundException, IOException}
import li.cil.oc.api import li.cil.oc.api
import li.cil.oc.api.fs.Mode import li.cil.oc.api.fs.Mode
import net.minecraft.nbt.{NBTTagList, NBTTagCompound} import net.minecraft.nbt.{NBTTagList, NBTTagCompound}
import scala.collection.mutable import scala.collection.mutable
import java.nio.channels.SeekableByteChannel
import java.nio.ByteBuffer
trait InputStreamFileSystem extends api.fs.FileSystem { trait InputStreamFileSystem extends api.fs.FileSystem {
private val handles = mutable.Map.empty[Int, Handle] private val handles = mutable.Map.empty[Int, Handle]
@ -25,9 +27,9 @@ trait InputStreamFileSystem extends api.fs.FileSystem {
override def open(path: String, mode: Mode) = this.synchronized(if (mode == Mode.Read && exists(path) && !isDirectory(path)) { override def open(path: String, mode: Mode) = this.synchronized(if (mode == Mode.Read && exists(path) && !isDirectory(path)) {
val handle = Iterator.continually((Math.random() * Int.MaxValue).toInt + 1).filterNot(handles.contains).next() val handle = Iterator.continually((Math.random() * Int.MaxValue).toInt + 1).filterNot(handles.contains).next()
openInputStream(path) match { openInputChannel(path) match {
case Some(stream) => case Some(channel) =>
handles += handle -> new Handle(this, handle, path, stream) handles += handle -> new Handle(this, handle, path, channel)
handle handle
case _ => throw new FileNotFoundException() case _ => throw new FileNotFoundException()
} }
@ -49,10 +51,10 @@ trait InputStreamFileSystem extends api.fs.FileSystem {
val handle = handleNbt.getInteger("handle") val handle = handleNbt.getInteger("handle")
val path = handleNbt.getString("path") val path = handleNbt.getString("path")
val position = handleNbt.getLong("position") val position = handleNbt.getLong("position")
openInputStream(path) match { openInputChannel(path) match {
case Some(stream) => case Some(channel) =>
val fileHandle = new Handle(this, handle, path, stream) val fileHandle = new Handle(this, handle, path, channel)
fileHandle.position = stream.skip(position) // May be != position if the file changed since we saved. channel.position(position)
handles += handle -> fileHandle handles += handle -> fileHandle
case _ => // The source file seems to have disappeared since last time. case _ => // The source file seems to have disappeared since last time.
} }
@ -62,7 +64,7 @@ trait InputStreamFileSystem extends api.fs.FileSystem {
override def save(nbt: NBTTagCompound) = this.synchronized { override def save(nbt: NBTTagCompound) = this.synchronized {
val handlesNbt = new NBTTagList() val handlesNbt = new NBTTagList()
for (file <- handles.values) { for (file <- handles.values) {
assert(!file.isClosed) assert(file.channel.isOpen)
val handleNbt = new NBTTagCompound() val handleNbt = new NBTTagCompound()
handleNbt.setInteger("handle", file.handle) handleNbt.setInteger("handle", file.handle)
handleNbt.setString("path", file.path) handleNbt.setString("path", file.path)
@ -74,33 +76,65 @@ trait InputStreamFileSystem extends api.fs.FileSystem {
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
protected def openInputStream(path: String): Option[InputStream] protected def openInputChannel(path: String): Option[SeekableByteChannel]
protected class InputFileChannel(val inputStream: java.io.InputStream) extends SeekableByteChannel {
var isOpen = true
private var position_ = 0L
override def close() = inputStream.close()
override def truncate(size: Long) = throw new java.io.IOException()
override def size() = inputStream.available()
override def position(newPosition: Long) = {
inputStream.reset()
position_ = inputStream.skip(newPosition)
this
}
override def position = position_
override def write(src: ByteBuffer) = throw new java.io.IOException()
override def read(dst: ByteBuffer): Int = {
if (dst.hasArray) {
inputStream.read(dst.array())
}
else {
val count = dst.limit - dst.position
for (i <- 0 until count) {
inputStream.read match {
case -1 => return i
case b => dst.put(b.toByte)
}
}
count
}
}
}
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
private class Handle(val owner: InputStreamFileSystem, val handle: Int, val path: String, val stream: InputStream) extends api.fs.Handle { private class Handle(val owner: InputStreamFileSystem, val handle: Int, val path: String, val channel: SeekableByteChannel) extends api.fs.Handle {
var isClosed = false override def position = channel.position
var position = 0L
override def length = owner.size(path) override def length = owner.size(path)
override def close() = if (!isClosed) { override def close() = if (channel.isOpen) {
isClosed = true
owner.handles -= handle owner.handles -= handle
stream.close() channel.close()
} }
override def read(into: Array[Byte]) = { override def read(into: Array[Byte]) = {
val read = stream.read(into) channel.read(ByteBuffer.wrap(into))
if (read >= 0)
position += read
read
} }
override def seek(to: Long) = { override def seek(to: Long) = {
stream.reset() channel.position(to)
position = stream.skip(to) channel.position
position
} }
override def write(value: Array[Byte]) = throw new IOException("bad file descriptor") override def write(value: Array[Byte]) = throw new IOException("bad file descriptor")

View File

@ -97,9 +97,9 @@ trait VirtualFileSystem extends OutputStreamFileSystem {
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
protected def openInputStream(path: String) = protected def openInputChannel(path: String) =
root.get(segments(path)) match { root.get(segments(path)) match {
case Some(obj: VirtualFile) => obj.openInputStream() case Some(obj: VirtualFile) => obj.openInputStream().map(new InputFileChannel(_))
case _ => None case _ => None
} }

View File

@ -58,8 +58,8 @@ class ZipFileInputStreamFileSystem(private val archive: ArchiveDirectory) extend
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
override protected def openInputStream(path: String) = ZipFileInputStreamFileSystem.synchronized { override protected def openInputChannel(path: String) = ZipFileInputStreamFileSystem.synchronized {
entry(path).map(_.openStream()) entry(path).map(entry => new InputFileChannel(entry.openStream()))
} }
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //