diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf index a649476b9..b008baa33 100644 --- a/src/main/resources/reference.conf +++ b/src/main/resources/reference.conf @@ -232,6 +232,10 @@ opencomputers { # theoretically use as much memory as they want. Only relevant when # using the native library. disableMemoryLimit: false + + # Force the Buffered FileSystem to be case insensitive. This makes it impossible + # To have 2 files whose names only differ by their capitalization + forceCaseInsensitiveFS: false } } diff --git a/src/main/scala/li/cil/oc/Settings.scala b/src/main/scala/li/cil/oc/Settings.scala index 13772b982..5b4835c1a 100644 --- a/src/main/scala/li/cil/oc/Settings.scala +++ b/src/main/scala/li/cil/oc/Settings.scala @@ -75,6 +75,7 @@ class Settings(config: Config) { val allowUserdata = !config.getBoolean("computer.debug.disableUserdata") val allowPersistence = !config.getBoolean("computer.debug.disablePersistence") val limitMemory = !config.getBoolean("computer.debug.disableMemoryLimit") + val forceCaseInsensitive = config.getBoolean("computer.debug.forceCaseInsensitiveFS") // ----------------------------------------------------------------------- // // robot diff --git a/src/main/scala/li/cil/oc/server/fs/FileSystem.scala b/src/main/scala/li/cil/oc/server/fs/FileSystem.scala index 2076159b4..ffe0f2b30 100644 --- a/src/main/scala/li/cil/oc/server/fs/FileSystem.scala +++ b/src/main/scala/li/cil/oc/server/fs/FileSystem.scala @@ -7,11 +7,15 @@ 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.{ComputerCraft15, ComputerCraft16, Mods} -import li.cil.oc.{Settings, api} +import li.cil.oc.{OpenComputers, Settings, api} import net.minecraft.nbt.NBTTagCompound import net.minecraftforge.common.DimensionManager +import java.util.UUID object FileSystem extends api.detail.FileSystemAPI { + + lazy val isCaseSensitive = !Settings.get.forceCaseInsensitive && calculateCaseSensitive + override def fromClass(clazz: Class[_], domain: String, root: String): api.fs.FileSystem = { val innerPath = ("/assets/" + domain + "/" + (root.trim + "/")).replace("//", "/") @@ -107,6 +111,17 @@ object FileSystem extends api.detail.FileSystemAPI { } } + private def calculateCaseSensitive = { + val uuid = UUID.randomUUID().toString + val checkFile1 = new io.File(DimensionManager.getCurrentSaveRootDirectory, uuid + "oc_rox") + val checkFile2 = new io.File(DimensionManager.getCurrentSaveRootDirectory, uuid + "OC_ROX") + checkFile2.exists() && checkFile2.delete() // this should NEVER happen but could also lead to VERY weird bugs + checkFile1.createNewFile() + val ret = checkFile2.exists() + checkFile1.delete() + ret + } + private class ReadOnlyFileSystem(protected val root: io.File) extends InputStreamFileSystem with FileInputStreamFileSystem @@ -130,20 +145,61 @@ object FileSystem extends api.detail.FileSystemAPI { // 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) = { + super.segments( + if (isCaseSensitive) { + path + } else { + "/" + toCaseInsensitive(withoutSourroundingSlashes(path), root) + } + ) + } + private def validatePath(path: String) = { + if (path.exists(invalidChars.contains)) { throw new java.io.IOException("path contains invalid characters") } - // TODO Add fix for #338. - // If on a system with case insensitive file systems, check if path - // already exists, if so return that name instead (i.e. re-use the - // existing path, with exact same casing). + path } + + private def withoutSourroundingSlashes(path: String) = { + val path2 = if (path.startsWith("/")) + path.substring(1) + else + path + if (path2.endsWith("/")) + path2.substring(0, path2.length - 1) + else + path2 + } + + private def toCaseInsensitive(path: String, node: VirtualDirectory): String = { + val idx = path.indexOf('/') + val first = if (idx == -1) path else path.substring(0, idx) + val rest = if (idx == -1) "" else path.substring(idx + 1) + + val lowerFirst = first.toLowerCase + var name = first + node.children.foreach { + case (childName, child) => + if (childName.toLowerCase == lowerFirst) { + child match { + case file: VirtualFile => + name = childName + "/" + rest + case dir: VirtualDirectory => + name = childName + "/" + toCaseInsensitive(rest, dir) + case abc: Object => + OpenComputers.log.warning(s"[WTF] when resolving case insensitive name, child was a ${abc.getClass.getName}") + } + } + } + + name + } } }