blob: e72586afd9a12537a3cda7d34c72bd5995c9ec1d [file] [log] [blame] [raw]
package li.cil.oc.server.fs
import java.io
import java.net.URL
import java.util.UUID
import li.cil.oc.api.driver.Container
import li.cil.oc.api.fs.{Label, Mode}
import li.cil.oc.server.component
import li.cil.oc.util.mods.{ComputerCraft, Mods}
import li.cil.oc.{OpenComputers, Settings, api}
import net.minecraft.nbt.NBTTagCompound
import net.minecraftforge.common.DimensionManager
import org.apache.logging.log4j.Level
object FileSystem extends api.detail.FileSystemAPI {
lazy val isCaseInsensitive = Settings.get.forceCaseInsensitive || (try {
val uuid = UUID.randomUUID().toString
val lowerCase = new io.File(DimensionManager.getCurrentSaveRootDirectory, uuid + "oc_rox")
val upperCase = new io.File(DimensionManager.getCurrentSaveRootDirectory, uuid + "OC_ROX")
// This should NEVER happen but could also lead to VERY weird bugs, so we
// make sure the files don't exist.
lowerCase.exists() && lowerCase.delete()
upperCase.exists() && upperCase.delete()
lowerCase.createNewFile()
val insensitive = upperCase.exists()
lowerCase.delete()
insensitive
}
catch {
case t: Throwable =>
// Among the security errors, createNewFile can throw an IOException.
// We just fall back to assuming case insensitive, since that's always
// safe in those cases.
OpenComputers.log.log(Level.WARN, "Couldn't determine if file system is case sensitive, falling back to insensitive.", t)
true
})
override def fromClass(clazz: Class[_], domain: String, root: String): api.fs.FileSystem = {
val innerPath = ("/assets/" + domain + "/" + (root.trim + "/")).replace("//", "/")
val codeSource = clazz.getProtectionDomain.getCodeSource.getLocation.getPath
val (codeUrl, isArchive) =
if (codeSource.contains(".zip!") || codeSource.contains(".jar!"))
(codeSource.substring(0, codeSource.lastIndexOf('!')), true)
else
(codeSource, false)
val file = try {
val url = new URL(codeUrl)
try {
new io.File(url.toURI)
}
catch {
case _: Throwable => new io.File(url.getPath)
}
} catch {
case _: Throwable => new io.File(codeSource)
}
if (isArchive) {
ZipFileInputStreamFileSystem.fromFile(file, innerPath.substring(1))
}
else {
if (!file.exists || file.isDirectory) return null
new io.File(new io.File(file.getParent), innerPath) match {
case fsp if fsp.exists() && fsp.isDirectory =>
new ReadOnlyFileSystem(fsp)
case _ =>
System.getProperty("java.class.path").split(System.getProperty("path.separator")).
find(cp => {
val fsp = new io.File(new io.File(cp), innerPath)
fsp.exists() && fsp.isDirectory
}) match {
case None => null
case Some(dir) => new ReadOnlyFileSystem(new io.File(new io.File(dir), innerPath))
}
}
}
}
override def fromSaveDirectory(root: String, capacity: Long, buffered: Boolean) = {
val path = new io.File(DimensionManager.getCurrentSaveRootDirectory, Settings.savePath + root)
if (!path.isDirectory) {
path.delete()
}
path.mkdirs()
if (path.exists() && path.isDirectory) {
if (buffered) new BufferedFileSystem(path, capacity)
else new ReadWriteFileSystem(path, capacity)
}
else null
}
def fromMemory(capacity: Long): api.fs.FileSystem = new RamFileSystem(capacity)
def fromComputerCraft(mount: AnyRef): api.fs.FileSystem = {
if (Mods.ComputerCraft.isAvailable) {
ComputerCraft.createFileSystem(mount).orNull
}
else null
}
def asManagedEnvironment(fileSystem: api.fs.FileSystem, label: Label, container: Container) =
Option(fileSystem).flatMap(fs => Some(new component.FileSystem(fs, label, Option(container)))).orNull
def asManagedEnvironment(fileSystem: api.fs.FileSystem, label: String, container: Container) =
asManagedEnvironment(fileSystem, new ReadOnlyLabel(label), container)
def asManagedEnvironment(fileSystem: api.fs.FileSystem, label: Label) =
Option(fileSystem).flatMap(fs => Some(new component.FileSystem(fs, label))).orNull
def asManagedEnvironment(fileSystem: api.fs.FileSystem, label: String) =
asManagedEnvironment(fileSystem, new ReadOnlyLabel(label))
def asManagedEnvironment(fileSystem: api.fs.FileSystem) =
asManagedEnvironment(fileSystem, null: Label)
private class ReadOnlyLabel(val label: String) extends Label {
def setLabel(value: String) = throw new IllegalArgumentException("label is read only")
def getLabel = label
override def load(nbt: NBTTagCompound) {}
override def save(nbt: NBTTagCompound) {
if (label != null) {
nbt.setString(Settings.namespace + "fs.label", label)
}
}
}
private class ReadOnlyFileSystem(protected val root: io.File)
extends InputStreamFileSystem
with FileInputStreamFileSystem
private class ReadWriteFileSystem(protected val root: io.File, protected val capacity: Long)
extends OutputStreamFileSystem
with FileOutputStreamFileSystem
with Capacity
private class RamFileSystem(protected val capacity: Long)
extends VirtualFileSystem
with Capacity
with Volatile
private class BufferedFileSystem(protected val fileRoot: io.File, protected val capacity: Long)
extends VirtualFileSystem
with Buffered
with Capacity {
// Worst-case: we're on Windows or using a FAT32 partition mounted in *nix.
// Note: we allow / as the path separator and expect all \s to be converted
// accordingly before the path is passed to the file system.
private val invalidChars = """\:*?"<>|""".toSet
override def makeDirectory(path: String) = super.makeDirectory(validatePath(path))
override protected def openOutputHandle(id: Int, path: String, mode: Mode) = super.openOutputHandle(id, validatePath(path), mode)
protected override def segments(path: String) = {
val parts = super.segments(path)
if (isCaseInsensitive) toCaseInsensitive(parts) else parts
}
private def validatePath(path: String) = {
if (path.exists(invalidChars.contains)) {
throw new java.io.IOException("path contains invalid characters")
}
path
}
private def toCaseInsensitive(path: Array[String]): Array[String] = {
var node = root
path.map(segment => {
assert(node != null, "corrupted virtual file system")
node.children.find(entry => entry._1.toLowerCase == segment.toLowerCase) match {
case Some((name, child: VirtualDirectory)) =>
node = child
name
case Some((name, child: VirtualFile)) =>
node = null
name
case _ => segment
}
})
}
}
}