Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.jtransc.vfs.syncvfs.kt Maven / Gradle / Ivy
/*
* Copyright 2016 Carlos Ballesteros Velasco
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jtransc.vfs
import com.jtransc.env.OS
import com.jtransc.error.*
import com.jtransc.io.ProcessUtils
import com.jtransc.io.readExactBytes
import com.jtransc.text.ToString
import com.jtransc.text.splitLast
import com.jtransc.vfs.node.FileNode
import com.jtransc.vfs.node.FileNodeTree
import java.io.*
import java.net.URL
import java.net.URLDecoder
import java.nio.charset.Charset
import java.util.*
import java.util.zip.GZIPInputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipInputStream
data class SyncVfsStat(
val file: SyncVfsFile,
val size: Long,
val mtime: Date,
val isDirectory: Boolean,
val isSymlink: Boolean,
val exists: Boolean,
val mode: FileMode,
val inode: Long = 0L
) {
val name: String get() = file.name
val path: String get() = file.path
val isFile: Boolean get() = !isDirectory
companion object {
fun notExists(file: SyncVfsFile) = SyncVfsStat(
file = file,
size = 0L,
mtime = Date(),
isDirectory = false,
isSymlink = false,
exists = false,
mode = FileMode.FULL_ACCESS
)
}
}
class SyncVfsFile(internal val vfs: SyncVfs, val path: String) {
val size: Long get() = stat().size
val mtime: Date get() = stat().mtime
fun setMtime(time: Date) = vfs.setMtime(path, time)
fun stat(): SyncVfsStat = vfs.stat(path)
fun chmod(mode: FileMode): Unit = vfs.chmod(path, mode)
fun symlinkTo(target: String): Unit = vfs.symlink(path, target)
fun read(): ByteArray = vfs.read(path)
fun readBytes(): ByteArray = read()
fun readOrNull(): ByteArray? = if (exists) read() else null
inline fun readSpecial(): T = readSpecial(T::class.java)
fun readSpecial(clazz: Class): T = vfs.readSpecial(clazz, path)
fun write(data: ByteArray): Unit = vfs.write(path, data)
fun readString(encoding: Charset = Charsets.UTF_8): String = encoding.toString(vfs.read(path))
fun readLines(encoding: Charset = Charsets.UTF_8): List = this.readString(encoding).lines()
val exists: Boolean get() = vfs.exists(path)
val isDirectory: Boolean get() = stat().isDirectory
fun remove(): Unit = vfs.remove(path)
fun removeIfExists(): Unit = if (exists) remove() else Unit
companion object {
val pathSeparator by lazy { System.getProperty("path.separator") ?: ":" }
val fileSeparator by lazy { System.getProperty("file.separator") ?: "/" }
}
fun getPaths(): List {
val env = getenv("PATH") ?: ""
return env.split(pathSeparator)
}
fun getenv(key: String): String? = vfs.getenv(key)
fun exec(cmdAndArgs: List, options: ExecOptions = ExecOptions()): ProcessResult = vfs.exec(path, cmdAndArgs.first(), cmdAndArgs.drop(1), options)
fun exec(cmd: String, args: List, options: ExecOptions): ProcessResult = vfs.exec(path, cmd, args, options)
fun exec(cmd: String, args: List, env: Map = mapOf()): ProcessResult = exec(cmd, args, ExecOptions(passthru = false, env = env))
fun exec(cmd: String, vararg args: String, env: Map = mapOf()): ProcessResult = exec(cmd, args.toList(), ExecOptions(passthru = false, env = env))
fun passthru(cmd: String, args: List, filter: ((line: String) -> Boolean)? = null, env: Map = mapOf()): ProcessResult = exec(cmd, args, ExecOptions(passthru = true, filter = filter, env = env))
fun passthru(cmd: String, vararg args: String, filter: ((line: String) -> Boolean)? = null, env: Map = mapOf()): ProcessResult = exec(cmd, args.toList(), ExecOptions(passthru = true, filter = filter, env = env))
val name: String get() = path.substringAfterLast('/')
val realpath: String get() = jailCombinePath(vfs.absolutePath, path)
val realpathOS: String get() = if (OS.isWindows) realpath.replace('/', '\\') else realpath
val realfile: File get() = File(realpathOS)
fun listdir(): Iterable = vfs.listdir(path)
fun listdirRecursive(): Iterable = listdirRecursive({ true })
fun listdirRecursive(filter: (stat: SyncVfsStat) -> Boolean): Iterable {
//log("REALPATH: ${this.realpath}")
return listdir().flatMap {
//log("item:${it.path}")
if (filter(it)) {
if (it.isDirectory) {
//log("directory! ${it.path}")
listOf(it) + it.file.listdirRecursive()
} else {
//log("file! ${it.path}")
listOf(it)
}
} else {
listOf()
}
}
}
fun firstRecursive(filter: (stat: SyncVfsStat) -> Boolean): SyncVfsStat {
for (item in listdirRecursive()) {
if (filter(item)) return item
}
invalidOp("No item on SyncVfsFile.firstRecursive")
}
fun mkdir(): Unit = vfs.mkdir(path)
fun rmdir(): Unit = vfs.rmdir(path)
fun ensuredir(): SyncVfsFile {
if (path == "") return this
if (!parent.exists) parent.ensuredir()
mkdir()
return this
}
fun ensureParentDir(): SyncVfsFile {
parent.ensuredir()
return this
}
fun rmdirRecursively(): Unit {
for (item in this.listdir()) {
if (item.isDirectory) {
item.file.rmdirRecursively()
} else {
item.file.remove()
}
}
this.rmdir()
}
fun rmdirRecursivelyIfExists(): Unit {
if (exists) return rmdirRecursively();
}
fun access(path: String): SyncVfsFile = SyncVfsFile(vfs, combinePaths(this.path, path));
operator fun get(path: String): SyncVfsFile = access(path)
operator fun set(path: String, content: String) = set(path, content.toByteArray(UTF8))
operator fun set(path: String, content: ToString) = set(path, content.toString().toByteArray(UTF8))
operator fun set(path: String, content: SyncVfsFile) = set(path, content.readBytes())
operator fun set(path: String, content: ByteArray) {
val file = access(path).ensureParentDir()
if (!file.exists || file.read() != content) {
file.write(content)
}
}
operator fun contains(path: String): Boolean = access(path).exists
fun jailAccess(path: String): SyncVfsFile = access(path).jail()
fun jail(): SyncVfsFile = AccessSyncVfs(vfs, path).root();
override fun toString() = "SyncVfsFile($vfs, '$path')"
fun write(data: String, encoding: Charset = UTF8): SyncVfsFile = writeString(data, encoding)
fun writeString(data: String, encoding: Charset = UTF8): SyncVfsFile {
write(data.toByteArray(encoding))
return this
}
fun dumpTree() {
println("")
for (path in listdirRecursive()) {
println(path)
}
println(" ")
}
fun copyTo(that: SyncVfsFile): Unit = that.ensureParentDir().write(this.read())
fun copyTreeTo(that: SyncVfsFile, filter: (from: SyncVfsFile, to: SyncVfsFile) -> Boolean = { from, to -> true }, doLog: Boolean = true): Unit {
if (doLog) com.jtransc.log.log("copyTreeTo " + this.realpath + " -> " + that.realpath)
val stat = this.stat()
if (stat.isDirectory) {
that.mkdir()
for (node in this.listdir()) {
node.file.copyTreeTo(that[node.name], filter, doLog = doLog)
}
} else {
this.copyTo(that)
}
}
fun toDumpString(): String {
return listdirRecursive().filter { it.isFile }.map { "// ${it.path}:\n${it.file.readString()}" }.joinToString("\n")
}
}
data class ExecOptions(
val passthru: Boolean = false,
val filter: ((line: String) -> Boolean)? = null,
val env: Map = mapOf(),
val sysexec: Boolean = false,
val fixencoding: Boolean = true,
val fixLineEndings: Boolean = true
) {
val redirect: Boolean get() = passthru
}
open class SyncVfs {
final fun root() = SyncVfsFile(this, "")
operator fun get(path: String) = root()[path]
open val absolutePath: String = ""
open fun read(path: String): ByteArray {
throw NotImplementedException()
}
open fun readSpecial(clazz: Class, path: String): T {
throw NotImplementedException()
}
open fun write(path: String, data: ByteArray): Unit {
throw NotImplementedException()
}
open fun listdir(path: String): Iterable {
throw NotImplementedException()
}
open fun mkdir(path: String): Unit {
throw NotImplementedException()
}
open fun rmdir(path: String): Unit {
throw NotImplementedException()
}
open fun getenv(key: String): String? {
return System.getenv(key)
}
open fun exec(path: String, cmd: String, args: List, options: ExecOptions): ProcessResult {
throw NotImplementedException()
}
open fun exists(path: String): Boolean {
try {
return stat(path).exists
} catch (e: Throwable) {
return false
}
}
open fun remove(path: String): Unit {
throw NotImplementedException()
}
open fun stat(path: String): SyncVfsStat {
val file = SyncVfsFile(this, path)
return try {
val data = read(path)
SyncVfsStat(
file = file,
size = data.size.toLong(),
mtime = Date(),
isDirectory = false,
exists = true,
isSymlink = false,
mode = FileMode.FULL_ACCESS
)
} catch (e: IOException) {
SyncVfsStat.notExists(file)
}
//throw NotImplementedException()
}
open fun chmod(path: String, mode: FileMode): Unit {
throw NotImplementedException()
}
open fun symlink(link: String, target: String): Unit {
throw NotImplementedException()
}
open fun setMtime(path: String, time: Date) {
throw NotImplementedException()
}
}
fun FileNode.toSyncStat(vfs: SyncVfs, path: String): SyncVfsStat {
return SyncVfsStat(
file = SyncVfsFile(vfs, path),
size = this.size(),
mtime = this.mtime(),
isDirectory = this.isDirectory(),
isSymlink = this.isSymlink(),
exists = true,
mode = this.mode()
)
}
open class _MemoryVfs : BaseTreeVfs(FileNodeTree()) {
}
private class _LocalVfs : SyncVfs() {
override val absolutePath: String get() = ""
override fun read(path: String): ByteArray = RawIo.fileRead(path)
override fun write(path: String, data: ByteArray): Unit = RawIo.fileWrite(path, data)
override fun listdir(path: String): Iterable = RawIo.listdir(path).map { it.toSyncStat(this, "$path/${it.name}") }
override fun mkdir(path: String): Unit = RawIo.mkdir(path)
override fun rmdir(path: String): Unit = RawIo.rmdir(path)
override fun exec(path: String, cmd: String, args: List, options: ExecOptions): ProcessResult = RawIo.execOrPassthruSync(path, cmd, args, options)
override fun exists(path: String): Boolean = RawIo.fileExists(path)
override fun remove(path: String): Unit = RawIo.fileRemove(path)
override fun stat(path: String): SyncVfsStat = RawIo.fileStat(path).toSyncStat(this, path)
override fun chmod(path: String, mode: FileMode): Unit = Unit.apply { RawIo.chmod(path, mode) }
override fun symlink(link: String, target: String): Unit = RawIo.symlink(link, target)
override fun setMtime(path: String, time: Date) = RawIo.setMtime(path, time)
}
fun File.toSyncStat(vfs: SyncVfs, path: String) = SyncVfsStat(
file = SyncVfsFile(vfs, path),
size = this.length(),
mtime = Date(this.lastModified()),
isDirectory = this.isDirectory,
//isSymlink = java.nio.file.Files.isSymbolicLink(java.nio.file.Paths.get(this.toURI())),
isSymlink = false,
exists = true,
mode = FileMode.FULL_ACCESS
)
abstract class ProxySyncVfs : SyncVfs() {
abstract protected fun transform(path: String): SyncVfsFile
open protected fun transformStat(stat: SyncVfsStat): SyncVfsStat = stat
override val absolutePath: String get() = transform("").realpath
override fun read(path: String): ByteArray = transform(path).read()
override fun readSpecial(clazz: Class, path: String): T = transform(path).readSpecial(clazz)
override fun write(path: String, data: ByteArray): Unit = transform(path).write(data)
// @TODO: Probably transform SyncVfsStat!
override fun listdir(path: String): Iterable = transform(path).listdir().map { transformStat(it) }
override fun mkdir(path: String): Unit {
transform(path).mkdir()
}
override fun rmdir(path: String): Unit {
transform(path).rmdir()
}
override fun exec(path: String, cmd: String, args: List, options: ExecOptions): ProcessResult = transform(path).exec(cmd, args, options)
override fun exists(path: String): Boolean = transform(path).exists
override fun remove(path: String): Unit = transform(path).remove()
override fun stat(path: String): SyncVfsStat = transformStat(transform(path).stat())
override fun chmod(path: String, mode: FileMode): Unit = transform(path).chmod(mode)
override fun symlink(link: String, target: String): Unit = transform(link).symlinkTo(transform(target).path)
override fun setMtime(path: String, time: Date) = transform(path).setMtime(time)
}
// @TODO: paths should not start with "/"
private class AccessSyncVfs(val parent: SyncVfs, val path: String) : ProxySyncVfs() {
override fun transform(path: String): SyncVfsFile = SyncVfsFile(parent, jailCombinePath(this.path, path))
override fun transformStat(stat: SyncVfsStat): SyncVfsStat {
// @TODO: Do this better!
val statFilePath = "/" + stat.file.path.trimStart('/')
val thisPath = "/" + this.path.trimStart('/')
if (!statFilePath.startsWith(thisPath)) {
throw InvalidOperationException("Assertion failed $statFilePath must start with $thisPath")
}
return SyncVfsStat(
file = SyncVfsFile(this, "/" + statFilePath.removePrefix(thisPath)),
size = stat.size,
mtime = stat.mtime,
isDirectory = stat.isDirectory,
isSymlink = stat.isSymlink,
exists = true,
mode = stat.mode
)
}
override fun toString(): String = "AccessSyncVfs($parent, $path)"
}
private class _LogSyncVfs(val parent: SyncVfs) : ProxySyncVfs() {
override fun transform(path: String): SyncVfsFile = SyncVfsFile(parent, path)
override fun write(path: String, data: ByteArray): Unit {
println("Writting $parent($path) with ${data.toString(UTF8)}")
super.write(path, data)
}
}
private class _UrlVfs : SyncVfs() {
override fun read(path: String): ByteArray {
val fixedUrl = Regex("^http(s?):/([^/])").replace(path, "http$1://$2")
val connection = URL(fixedUrl).openConnection()
connection.allowUserInteraction = false
connection.connectTimeout = 10000
connection.readTimeout = 10000
connection.addRequestProperty("Accept-Language", "en-US,en;q=0.8");
connection.addRequestProperty("User-Agent", "Mozilla");
//connection.addRequestProperty("Referer", fixedUrl);
//println(connection.headerFields)
val sin = connection.inputStream;
val sout = ByteArrayOutputStream();
sin.copyTo(sout)
return sout.toByteArray()
}
}
fun RootUrlVfs(): SyncVfsFile = _UrlVfs().root()
fun UrlVfs(url: String): SyncVfsFile = _UrlVfs().root().jailAccess(url)
fun UrlVfs(url: URL): SyncVfsFile = _UrlVfs().root().jailAccess(url.toExternalForm())
fun RootLocalVfs(): SyncVfsFile = _LocalVfs().root()
fun MergeVfs(nodes: List) = if (nodes.isNotEmpty()) MergedSyncVfs(nodes).root() else MemoryVfs()
fun MergedLocalAndJars(paths: List) = MergeVfs(LocalAndJars(paths))
fun LocalAndJars(paths: List): List {
return paths.map { if (it.endsWith(".jar")) ZipVfs(it) else LocalVfs(File(it)) }
}
fun CompressedVfs(file: File): SyncVfsFile {
val npath = file.absolutePath.toLowerCase()
return if (npath.endsWith(".tar.gz")) {
TarVfs(GZIPInputStream(file.inputStream()).readBytes())
} else if (npath.endsWith(".zip") || npath.endsWith(".jar")) {
ZipVfs(file)
} else if (npath.endsWith(".tar")) {
TarVfs(file)
} else {
invalidOp("Don't know how to handle compressed file: $file")
}
}
/*
fun ZipVfs(path: String): SyncVfsFile = ZipSyncVfs(ZipFile(path)).root()
fun ZipVfs(file: File): SyncVfsFile = ZipSyncVfs(ZipFile(file)).root()
fun ZipVfs(content: ByteArray): SyncVfsFile {
val tempFile = createTempFile("jtransc-zip")
tempFile.writeBytes(content)
return ZipSyncVfs(ZipFile(tempFile)).root()
}
*/
fun ResourcesVfs(clazz: Class<*>): SyncVfsFile = ResourcesSyncVfs(clazz).root()
@Deprecated("Use File instead", ReplaceWith("LocalVfs(File(path))", "java.io.File"))
fun LocalVfs(path: String): SyncVfsFile = RootLocalVfs().access(path).jail()
fun UnjailedLocalVfs(file: File): SyncVfsFile = RootLocalVfs().access(file.absolutePath)
fun LocalVfs(file: File): SyncVfsFile = _LocalVfs().root().access(file.absolutePath).jail()
fun LocalVfsEnsureDirs(file: File): SyncVfsFile {
ignoreErrors { file.mkdirs() }
return _LocalVfs().root().access(file.absolutePath).jail()
}
fun CwdVfs(): SyncVfsFile = LocalVfs(File(RawIo.cwd()))
fun CwdVfs(path: String): SyncVfsFile = CwdVfs().jailAccess(path)
fun ScriptVfs(): SyncVfsFile = LocalVfs(File(RawIo.script()))
fun MemoryVfs(): SyncVfsFile = _MemoryVfs().root()
fun MemoryVfs(vararg files: Pair): SyncVfsFile {
val vfs = _MemoryVfs().root()
for (file in files) {
vfs.access(file.first).ensureParentDir().writeString(file.second)
}
return vfs
}
fun GetClassJar(clazz: Class<*>): File {
val classLoader = VfsPath::class.java.classLoader
val classFilePath = clazz.name.replace('.', '/') + ".class"
//println(classFilePath)
//println(classLoader)
val classUrl = classLoader.getResource(classFilePath)
//path.
//println(path)
val regex = Regex("^file:(.*?)!(.*?)$")
val result = regex.find(classUrl.path)!!
val jarPath = result.groups[1]!!.value
return File(URLDecoder.decode(jarPath, Charsets.UTF_8.name()))
}
fun MemoryVfsBin(vararg files: Pair): SyncVfsFile {
val vfs = _MemoryVfs().root()
for (file in files) {
vfs.access(file.first).ensureParentDir().write(file.second)
}
return vfs
}
fun MemoryVfsFile(content: String, name: String = "file"): SyncVfsFile {
return MemoryVfs(name to content).access(name)
}
fun MemoryVfsFileBin(content: ByteArray, name: String = "file"): SyncVfsFile {
return MemoryVfsBin(name to content).access(name)
}
fun LogVfs(parent: SyncVfsFile): SyncVfsFile = _LogSyncVfs(parent.jail().vfs).root()
fun SyncVfsFile.log() = LogVfs(this)
fun normalizePath(path: String): String {
val out = ArrayList();
for (chunk in path.replace('\\', '/').split('/')) {
when (chunk) {
".." -> if (out.size > 0) out.removeAt(0)
"." -> Unit
"" -> if (out.size == 0) out.add("")
else -> out.add(chunk)
}
}
return out.joinToString("/")
}
fun combinePaths(vararg paths: String): String {
return normalizePath(paths.filter { it != "" }.joinToString("/"))
}
fun jailCombinePath(base: String, access: String): String {
return combinePaths(base, normalizePath(access))
}
class VfsPath(val path: String)
data class UserKey(val name: String)
inline fun UserKey(): UserKey = UserKey(T::class.java.name)
interface IUserData {
operator fun contains(key: UserKey): Boolean
operator fun get(key: UserKey): T?
operator fun set(key: UserKey, value: T)
}
fun IUserData.getCached(key: UserKey, builder: () -> T): T {
if (key !in this) this[key] = builder()
return this[key]!!
}
@Suppress("UNCHECKED_CAST")
class UserData : IUserData {
private val dict = hashMapOf()
override operator fun contains(key: UserKey): Boolean = key in dict
override operator fun get(key: UserKey): T? = dict[key] as T?
override operator fun set(key: UserKey, value: T) {
dict.put(key, value)
}
}
object Path {
fun parent(path: String): String = if (path.contains('/')) path.substringBeforeLast('/') else ""
fun withoutExtension(path: String): String = path.substringBeforeLast('.')
fun withExtension(path: String, ext: String): String = withoutExtension(path) + ".$ext"
fun withBaseName(path: String, name: String): String = "${parent(path)}/$name"
}
val SyncVfsFile.parent: SyncVfsFile get() {
//println("Path: '${path}', Parent: '${Path.parent(path)}'")
return SyncVfsFile(vfs, Path.parent(path))
}
val SyncVfsFile.withoutExtension: SyncVfsFile get() = SyncVfsFile(vfs, Path.withoutExtension(path))
fun SyncVfsFile.withExtension(ext: String): SyncVfsFile = SyncVfsFile(vfs, Path.withExtension(path, ext))
fun SyncVfsFile.withBaseName(baseName: String): SyncVfsFile = parent.access(baseName)
private class MergedSyncVfs(val nodes: List) : SyncVfs() {
init {
if (nodes.isEmpty()) throw InvalidArgumentException("Nodes can't be empty")
}
override val absolutePath: String = "#merged#"
//private val nodesSorted = nodes.reversed()
val nodesSorted = nodes
private fun op(path: String, act: String, action: (node: SyncVfsFile) -> T): T {
var lastError: Throwable? = null
for (node in nodesSorted) {
try {
return action(node)
} catch(t: Throwable) {
lastError = t
}
}
throw RuntimeException("Can't $act file '$path' : $lastError")
}
override fun read(path: String): ByteArray = op(path, "read") { it[path].read() }
override fun write(path: String, data: ByteArray) = op(path, "write") { it[path].write(data) }
override fun readSpecial(clazz: Class, path: String): T = op(path, "readSpecial") { it[path].readSpecial(clazz) }
override fun listdir(path: String): Iterable {
//op(path, "listdir") { it[path].listdir() }
return nodesSorted.flatMap { it.listdir() }
}
override fun mkdir(path: String) = op(path, "mkdir") { it[path].mkdir() }
override fun rmdir(path: String) = op(path, "rmdir") { it[path].rmdir() }
override fun exec(path: String, cmd: String, args: List, options: ExecOptions): ProcessResult {
return op(path, "exec") { it[path].exec(cmd, args, options) }
}
override fun remove(path: String) = op(path, "remove") { it[path].remove() }
override fun stat(path: String): SyncVfsStat {
var lastStat: SyncVfsStat? = null
for (node in nodesSorted) {
val stat = node[path].stat()
lastStat = stat
if (stat.exists) break
//println(stat)
}
return lastStat!!
}
override fun setMtime(path: String, time: Date) = op(path, "setMtime") { it[path].setMtime(time) }
override fun toString(): String = "MergedSyncVfs(" + this.nodes.joinToString(", ") + ")"
}
private class ResourcesSyncVfs(val clazz: Class<*>) : SyncVfs() {
val classLoader = clazz.classLoader
override fun read(path: String): ByteArray {
return classLoader.getResourceAsStream(path).readBytes()
}
}
fun SyncVfsFile.getUnmergedFiles(): List {
val vfs = this.vfs
if (vfs is MergedSyncVfs) {
return vfs.nodes.map { it[this.path] }
} else {
return listOf(this)
}
}