diff --git a/base/src/main/kotlin/de/staropensource/engine/base/Engine.kt b/base/src/main/kotlin/de/staropensource/engine/base/Engine.kt
index b8241b5..4ae9368 100644
--- a/base/src/main/kotlin/de/staropensource/engine/base/Engine.kt
+++ b/base/src/main/kotlin/de/staropensource/engine/base/Engine.kt
@@ -21,6 +21,7 @@
package de.staropensource.engine.base
import de.staropensource.engine.base.utility.Environment
+import de.staropensource.engine.base.utility.FileAccess
import de.staropensource.engine.logging.Logger
/**
@@ -120,6 +121,7 @@ class Engine private constructor() {
// Run initialization code
Environment.detect()
+ FileAccess.updateDefaultPaths()
state = State.INITIALIZED
}
@@ -142,6 +144,7 @@ class Engine private constructor() {
// Run shutdown code
Environment.unset()
+ FileAccess.unsetDefaultPaths()
state = State.SHUT_DOWN
}
diff --git a/base/src/main/kotlin/de/staropensource/engine/base/exception/FileOrDirectoryNotFoundException.kt b/base/src/main/kotlin/de/staropensource/engine/base/exception/FileOrDirectoryNotFoundException.kt
new file mode 100644
index 0000000..c73c110
--- /dev/null
+++ b/base/src/main/kotlin/de/staropensource/engine/base/exception/FileOrDirectoryNotFoundException.kt
@@ -0,0 +1,29 @@
+/*
+ * STAROPENSOURCE ENGINE SOURCE FILE
+ * Copyright (c) 2024 The StarOpenSource Engine Authors
+ * Licensed under the GNU Affero General Public License v3
+ * with an exception allowing classpath linking.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.staropensource.engine.base.exception
+
+/**
+ * Thrown when being unable to
+ * find a file or directory.
+ *
+ * @since v1-alpha10
+ */
+class FileOrDirectoryNotFoundException(val path: String, val throwable: Throwable? = null) : RuntimeException("The file or directory '${path}' could not be found", throwable)
diff --git a/base/src/main/kotlin/de/staropensource/engine/base/exception/FileTooLargeException.kt b/base/src/main/kotlin/de/staropensource/engine/base/exception/FileTooLargeException.kt
new file mode 100644
index 0000000..ec82362
--- /dev/null
+++ b/base/src/main/kotlin/de/staropensource/engine/base/exception/FileTooLargeException.kt
@@ -0,0 +1,30 @@
+/*
+ * STAROPENSOURCE ENGINE SOURCE FILE
+ * Copyright (c) 2024 The StarOpenSource Engine Authors
+ * Licensed under the GNU Affero General Public License v3
+ * with an exception allowing classpath linking.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.staropensource.engine.base.exception
+
+/**
+ * Thrown when reading a file fails
+ * due to it being larger than the
+ * configured max heap size.
+ *
+ * @since v1-alpha10
+ */
+class FileTooLargeException(val path: String, val throwable: Throwable? = null) : RuntimeException("Unable to read file '${path}' as it is larger than the configured heap size", throwable)
diff --git a/base/src/main/kotlin/de/staropensource/engine/base/exception/IOAccessException.kt b/base/src/main/kotlin/de/staropensource/engine/base/exception/IOAccessException.kt
new file mode 100644
index 0000000..615f38c
--- /dev/null
+++ b/base/src/main/kotlin/de/staropensource/engine/base/exception/IOAccessException.kt
@@ -0,0 +1,28 @@
+/*
+ * STAROPENSOURCE ENGINE SOURCE FILE
+ * Copyright (c) 2024 The StarOpenSource Engine Authors
+ * Licensed under the GNU Affero General Public License v3
+ * with an exception allowing classpath linking.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.staropensource.engine.base.exception
+
+/**
+ * Thrown when an IO error occurs.
+ *
+ * @since v1-alpha10
+ */
+class IOAccessException(val error: String? = null, val throwable: Throwable? = null) : RuntimeException(error, throwable)
diff --git a/base/src/main/kotlin/de/staropensource/engine/base/exception/VerificationFailedException.kt b/base/src/main/kotlin/de/staropensource/engine/base/exception/VerificationFailedException.kt
new file mode 100644
index 0000000..2518859
--- /dev/null
+++ b/base/src/main/kotlin/de/staropensource/engine/base/exception/VerificationFailedException.kt
@@ -0,0 +1,28 @@
+/*
+ * STAROPENSOURCE ENGINE SOURCE FILE
+ * Copyright (c) 2024 The StarOpenSource Engine Authors
+ * Licensed under the GNU Affero General Public License v3
+ * with an exception allowing classpath linking.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.staropensource.engine.base.exception
+
+/**
+ * Thrown when a verification fails.
+ *
+ * @since v1-alpha10
+ */
+class VerificationFailedException(val error: String? = null, val throwable: Throwable? = null) : RuntimeException(error, throwable)
diff --git a/base/src/main/kotlin/de/staropensource/engine/base/exception/package-info.kt b/base/src/main/kotlin/de/staropensource/engine/base/exception/package-info.kt
new file mode 100644
index 0000000..e4d0585
--- /dev/null
+++ b/base/src/main/kotlin/de/staropensource/engine/base/exception/package-info.kt
@@ -0,0 +1,26 @@
+/*
+ * STAROPENSOURCE ENGINE SOURCE FILE
+ * Copyright (c) 2024 The StarOpenSource Engine Authors
+ * Licensed under the GNU Affero General Public License v3
+ * with an exception allowing classpath linking.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+/**
+ * Exceptions thrown by the engine.
+ *
+ * @since v1-alpha10
+ */
+package de.staropensource.engine.base.exception
diff --git a/base/src/main/kotlin/de/staropensource/engine/base/utility/FileAccess.kt b/base/src/main/kotlin/de/staropensource/engine/base/utility/FileAccess.kt
new file mode 100644
index 0000000..1aa8153
--- /dev/null
+++ b/base/src/main/kotlin/de/staropensource/engine/base/utility/FileAccess.kt
@@ -0,0 +1,1394 @@
+/*
+ * STAROPENSOURCE ENGINE SOURCE FILE
+ * Copyright (c) 2024 The StarOpenSource Engine Authors
+ * Licensed under the GNU Affero General Public License v3
+ * with an exception allowing classpath linking.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.staropensource.engine.base.utility
+
+import de.staropensource.engine.base.Engine.Companion.logger
+import de.staropensource.engine.base.annotation.NonKotlinContact
+import de.staropensource.engine.base.exception.FileOrDirectoryNotFoundException
+import de.staropensource.engine.base.exception.FileTooLargeException
+import de.staropensource.engine.base.exception.IOAccessException
+import de.staropensource.engine.base.exception.VerificationFailedException
+import de.staropensource.engine.base.utility.Environment.OperatingSystem.*
+import de.staropensource.engine.base.utility.FileAccess.Companion.configDirectory
+import de.staropensource.engine.base.utility.FileAccess.Companion.format
+import java.io.File
+import java.io.IOException
+import java.nio.file.*
+import java.nio.file.attribute.BasicFileAttributes
+import java.nio.file.attribute.PosixFilePermissions
+
+/**
+ * Provides a simplified way of
+ * accessing files and directories.
+ *
+ * @since v1-alpha10
+ */
+@Suppress("unused")
+class FileAccess {
+ /**
+ * Companion object of [FileAccess].
+ *
+ * @since v1-alpha10
+ */
+ companion object {
+ // -----> Default paths
+ /**
+ * [Path]s which have been scheduled
+ * for deletion at engine shutdown.
+ *
+ * @since v1-alpha10
+ */
+ @JvmStatic
+ private var scheduledDeletion: MutableList = mutableListOf()
+
+ /**
+ * [FileAccess] instance for the temporary
+ * cache directory. It is created by the
+ * engine at startup and is pruned at
+ * engine shutdown.
+ *
+ * @since v1-alpha10
+ */
+ @JvmStatic
+ var temporaryCacheDirectory: FileAccess? = null
+ private set
+
+ /**
+ * [FileAccess] instance for the persistent
+ * cache directory of the system.
+ *
+ * The directory in this variable will
+ * not be pruned unless the user decides
+ * to do so. This may involve running the
+ * system's cleanup tools or the user
+ * manually finding and deleting the cache.
+ *
+ * @since v1-alpha10
+ */
+ @JvmStatic
+ var persistentCacheDirectory: FileAccess? = null
+ private set
+
+ /**
+ * [FileAccess] instance to the
+ * user's home directory.
+ *
+ * @since v1-alpha10
+ */
+ @JvmStatic
+ var homeDirectory: FileAccess? = null
+ private set
+
+ /**
+ * [FileAccess] instance to the directory
+ * in which applications can store their
+ * configuration files.
+ *
+ * @since v1-alpha10
+ */
+ @JvmStatic
+ var configDirectory: FileAccess? = null
+ private set
+
+ /**
+ * [FileAccess] instance to the directory
+ * in which applications can store their
+ * data files.
+ *
+ * For storing configuration files,
+ * see [configDirectory] instead.
+ *
+ * @since v1-alpha10
+ */
+ @JvmStatic
+ var dataDirectory: FileAccess? = null
+ private set
+
+ /**
+ * Unsets all default paths.
+ *
+ * @since v1-alpha10
+ */
+ internal fun unsetDefaultPaths() {
+ temporaryCacheDirectory = null
+ persistentCacheDirectory = null
+ homeDirectory = null
+ configDirectory = null
+ dataDirectory = null
+ }
+
+ /**
+ * Updates all default paths to
+ * their platform-specific path.
+ *
+ * @since v1-alpha10
+ */
+ internal fun updateDefaultPaths() {
+ logger.diag("Updating default paths")
+
+ // Storage
+ homeDirectory = FileAccess(System.getProperty("user.home")).createDirectory()
+ configDirectory = FileAccess(when (Environment.operatingSystem) {
+ LINUX, FREEBSD, NETBSD, OPENBSD -> "${homeDirectory}/.config"
+ WINDOWS -> "${homeDirectory}/AppData/Roaming/sosengine-config"
+ else -> "${homeDirectory}/.sosengine/config"
+ }).createDirectory()
+ dataDirectory = FileAccess(when (Environment.operatingSystem) {
+ LINUX, FREEBSD, NETBSD, OPENBSD -> "${homeDirectory}/.local/share"
+ WINDOWS -> "${homeDirectory}/AppData/Roaming/sosengine-data"
+ else -> "${homeDirectory}/.sosengine/data"
+ }).createDirectory()
+ // Caches
+ temporaryCacheDirectory = FileAccess(
+ System.getProperty("java.io.tmpdir")
+ + "/sosengine-cache-"
+ + ProcessHandle.current().pid()
+ ).createDirectory().deleteOnShutdown()
+ persistentCacheDirectory = FileAccess(when (Environment.operatingSystem) {
+ LINUX, FREEBSD, NETBSD, OPENBSD -> "${homeDirectory}/.cache"
+ WINDOWS -> "${homeDirectory}/AppData/Local/Temp"
+ else -> "${homeDirectory}/.sosengine/persistent-cache"
+ }).createDirectory()
+ }
+
+
+ // -----> Maintenance
+ /**
+ * Deletes all files and directories
+ * scheduled for deletion.
+ *
+ * @since v1-alpha10
+ */
+ internal fun deleteScheduled() {
+ logger.verb("Deleting files scheduled for deletion")
+
+ for (path: Path in scheduledDeletion)
+ try {
+ Files
+ .walk(path)
+ .use {
+ logger.diag("Deleting file or directory '${unformatFromPath(path)}' scheduled for deletion")
+
+ // Delete all files recursively
+ // Only applies to directories
+ it.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete)
+
+ // Delete file or directory
+ if (Files.exists(path))
+ if (!path.toFile().delete())
+ logger.sarn("Unable to delete file or directory '${unformatFromPath(path)}' scheduled for deletion manually")
+ }
+ } catch (exception: Exception) {
+ // TODO add exception printing
+ logger.sarn("Unable to delete file or directory '${unformatFromPath(path)}' scheduled for deletion.")
+ }
+ }
+
+
+ // -----> Path formatting
+ /**
+ * Formats the specified string path for use
+ * in conjunction with [Path] and [File].
+ *
+ * @param string string to format
+ * @return formatted string
+ * @since v1-alpha10
+ */
+ fun format(string: String): String {
+ return string
+ .replace("\\", "/")
+ .replace("/./", "/")
+ .replace("/", File.separator)
+ }
+
+ /**
+ * Formats the specified string path
+ * and returns a matching [Path] instance.
+ *
+ * @param string string to format
+ * @return matching [Path]
+ * @since v1-alpha10
+ */
+ fun formatToPath(string: String): @NonKotlinContact Path = Path.of(format(string))
+
+ /**
+ * Undoes formatting made by [format]
+ * and returns a simplified string path.
+ *
+ * @param string string path to undo formatting for
+ * @return unformatted string path
+ * @since v1-alpha10
+ */
+ fun unformat(string: String): String = string.replace(File.separator, "/")
+
+ /**
+ * Converts and undoes formatting for a [Path]
+ * instance and returns a simplified string path.
+ *
+ * @param path [Path] to undo formatting for
+ * @return unformatted string path
+ * @since v1-alpha10
+ */
+ fun unformatFromPath(path: @NonKotlinContact Path): String = unformat(path.toString())
+ }
+
+
+ // -----> Instance metadata
+ /**
+ * Contains the [Path] to the file
+ * or directory represented by
+ * this [FileAccess] instance.
+ *
+ * Use methods provided by
+ * [FileAccess] if possible.
+ *
+ * @since v1-alpha10
+ */
+ val path: @NonKotlinContact Path
+ @JvmName(name = "getJavaPath")
+ get
+
+ /**
+ * Contains the [File] to the file
+ * or directory represented by
+ * this [FileAccess] instance.
+ *
+ * Use methods provided by
+ * [FileAccess] if possible.
+ *
+ * @since v1-alpha10
+ */
+ private val file: @NonKotlinContact File
+ @JvmName(name = "getJavaFile")
+ get
+
+
+ // -----> Constructors
+ /**
+ * Opens the specified file.
+ *
+ * @param path path to the file to open
+ * @since v1-alpha10
+ */
+ constructor(path: String) {
+ this.path = formatToPath(path).toAbsolutePath()
+ this.file = this.path.toFile()
+ }
+
+ /**
+ * Opens the specified file.
+ *
+ * @param path path to the file to open
+ * @since v1-alpha10
+ */
+ constructor(path: Path) {
+ this.path = path.toAbsolutePath()
+ this.file = this.path.toFile()
+ }
+
+ /**
+ * Opens the specified file.
+ *
+ * @param path path to the file to open
+ * @throws InvalidPathException if a [Path] cannot be created (see [java.nio.file.FileSystem.getPath])
+ * @since v1-alpha10
+ */
+ constructor(file: File) {
+ this.path = file.toPath().toAbsolutePath()
+ this.file = file
+ }
+
+
+ // -----> Getters
+ /**
+ * Returns if this file or directory exists.
+ *
+ * @return exists?
+ * @throws IOAccessException on IO error
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class)
+ fun exists(): Boolean {
+ return Files.exists(path)
+ }
+
+ /**
+ * Returns the type of this file or directory.
+ *
+ * @return type
+ * @throws IOAccessException on IO error
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class)
+ fun getType(): Type {
+ return if (!exists()) Type.VOID
+ else if (Files.isRegularFile(path)) Type.FILE
+ else if (Files.isDirectory(path)) Type.DIRECTORY
+ else Type.UNKNOWN
+ }
+
+ /**
+ * Returns if this file or
+ * directory is a symbolic link.
+ *
+ * @return is a symbolic link?
+ * @throws IOAccessException on IO error
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class)
+ fun isSymbolicLink(): Boolean {
+ try {
+ return Files.isSymbolicLink(path)
+ } catch (exception: Exception) {
+ throw IOAccessException("Checking if '${unformatFromPath(path)}' is a symbolic link failed", exception)
+ }
+ }
+
+ /**
+ * Returns if this file or
+ * directory is hidden.
+ *
+ * @return is hidden?
+ * @throws IOAccessException on IO error
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class)
+ fun isHidden(): Boolean {
+ try {
+ return Files.isHidden(path)
+ } catch (exception: Exception) {
+ throw IOAccessException("Checking if '${unformatFromPath(path)}' is hidden failed", exception)
+ }
+ }
+
+ /**
+ * Returns if this file can be read from.
+ *
+ * @return readable?
+ * @throws IOAccessException on IO error
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class)
+ fun isReadable(): Boolean {
+ try {
+ return Files.isReadable(path)
+ } catch (exception: Exception) {
+ throw IOAccessException("Checking if '${unformatFromPath(path)}' is readable failed", exception)
+ }
+ }
+
+ /**
+ * Returns if this file can be written to.
+ *
+ * @return writable?
+ * @throws IOAccessException on IO error
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class)
+ fun isWritable(): Boolean {
+ try {
+ return Files.isWritable(path)
+ } catch (exception: Exception) {
+ throw IOAccessException("Checking if '${unformatFromPath(path)}' is writable failed", exception)
+ }
+ }
+
+ /**
+ * Returns if this file can be executed.
+ *
+ * @return executable?
+ * @throws IOAccessException on IO error
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class)
+ fun isExecutable(): Boolean {
+ try {
+ return Files.isExecutable(path)
+ } catch (exception: Exception) {
+ throw IOAccessException("Checking if '${unformatFromPath(path)}' is executable failed", exception)
+ }
+ }
+
+ /**
+ * Returns the file's permissions
+ * in the POSIX `rwxrwxrwx` format.
+ *
+ * @param fakeOnUnsupported if the permission format shall be faked on platforms not supporting POSIX file permissions. `null` will be returned if set to `false`
+ * @return file permissions in the POSIX permission format or `false` if unsupported and [fakeOnUnsupported] is `false`
+ * @throws IOAccessException on IO error
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class)
+ fun getPosixPermissions(fakeOnUnsupported: Boolean = true): String? {
+ try {
+ return PosixFilePermissions.toString(Files.getPosixFilePermissions(path))
+ } catch (exception: UnsupportedOperationException) {
+ if (fakeOnUnsupported) {
+ val builder: StringBuilder = StringBuilder()
+
+ // Add permissions
+ if (isReadable()) builder.append("r")
+ if (isWritable()) builder.append("w")
+ if (isExecutable()) builder.append("x")
+
+ // Repeat two times to match the format
+ builder.repeat(2)
+
+ return builder.toString()
+ } else
+ return null
+ } catch (exception: Exception) {
+ throw IOAccessException(exception.message, exception.cause)
+ }
+ }
+
+
+ // -----> Path getters
+ /**
+ * Returns the absolute path
+ * of this file or directory.
+ *
+ * @return absolute path
+ * @since v1-alpha10
+ */
+ override fun toString(): String = unformatFromPath(path)
+
+ /**
+ * Returns the absolute raw
+ * path as seen by the JVM.
+ *
+ * @return absolute raw path
+ * @since v1-alpha10
+ */
+ fun toStringRaw(): String = path.toString()
+
+ /**
+ * Returns the base name of
+ * this file or directory.
+ *
+ * @param excludeExtension if to exclude the file extension (e.g. `.txt`, `.java`, `.kt`, etc.), if found
+ * @since v1-alpha10
+ */
+ fun getBaseName(excludeExtension: Boolean = false): String {
+ return if (excludeExtension) file.name.replaceFirst("[.][^.]+$", "")
+ else file.name
+ }
+
+ /**
+ * Returns the destination of
+ * this symbolic or hard link.
+ *
+ * @return destination or `null` if not a symbolic or hard link
+ * @throws IOAccessException on IO error
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class)
+ fun getLinkDestination(): FileAccess? {
+ return try {
+ FileAccess(Files.readSymbolicLink(path))
+ } catch (exception: Exception) {
+ when (exception) {
+ is NotLinkException, is UnsupportedOperationException -> null
+ else -> throw exception
+ }
+ }
+ }
+
+
+ // -----> Filesystem getters
+ /**
+ * Returns the file system of this file.
+ *
+ * @return filesystem
+ * @since v1-alpha10
+ */
+ fun getFileSystem(): @NonKotlinContact FileSystem = path.fileSystem
+
+ /**
+ * Returns if the filesystem this file
+ * or directory is on is POSIX-compliant.
+ *
+ * @return POSIX-compliant?
+ * @since v1-alpha10
+ */
+ fun isFilesystemPosixCompliant(): Boolean {
+ return path.fileSystem.supportedFileAttributeViews().contains("posix")
+ }
+
+ /**
+ * Returns a regular expression matching
+ * invalid file names on the filesystem
+ * this file or directory is on.
+ *
+ * @return regex matching all invalid file names
+ * @since v1-alpha10
+ */
+ fun getFilesystemRestrictedNames(): String {
+ return when (Environment.operatingSystem) {
+ WINDOWS -> "(?i)\\|/|:|[*]|[?]|\"|<|>|[|]|CON|PRN|AUX|NUL|COM[0-9]|LPT[0-9]"
+ else -> "/"
+ }
+ }
+
+
+ // -----> File creation, moving, copying and deletion
+ /**
+ * Creates a file at this location.
+ *
+ * If something already exists at this
+ * location, then nothing will be done.
+ *
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @see verifyIsFile
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class)
+ fun createFile(): FileAccess {
+ if (!exists())
+ try {
+ logger.diag("Creating a file at '${unformatFromPath(path)}'")
+ file.parentFile.mkdirs()
+ file.createNewFile()
+ } catch (exception: Exception) {
+ throw IOAccessException("Unable to create a new file at '${unformatFromPath(path)}'", exception)
+ }
+
+ return this
+ }
+
+ /**
+ * Creates a directory at this location.
+ *
+ * If something already exists at this
+ * location, then nothing will be done.
+ *
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @see verifyIsDirectory
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class)
+ fun createDirectory(): FileAccess {
+ if (!exists())
+ try {
+ logger.diag("Creating a directory at '${unformatFromPath(path)}'")
+ file.mkdirs()
+ } catch (exception: Exception) {
+ throw IOAccessException("Unable to create a new directory at '${unformatFromPath(path)}'", exception)
+ }
+
+ return this
+ }
+
+ /**
+ * Creates a link at this location.
+ *
+ * If something already exists at this
+ * location, then nothing will be done.
+ *
+ * @param destination where to link to
+ * @param hard if the link should be hard (`true`) or symbolic (`false`). For an explanation on the two, see [symlink(7)](https://man7.org/linux/man-pages/man7/symlink.7.html)
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @see verifyIsLink
+ * @see verifyIsSymbolicLink
+ * @see verifyIsHardLink
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class)
+ fun createLink(destination: String, hard: Boolean): FileAccess {
+ if (!exists())
+ try {
+ logger.diag("Creating a ${if (hard) "hard" else "symbolic"} link at '${unformatFromPath(path)}'")
+ if (hard) Files.createLink(path, formatToPath(destination))
+ else Files.createSymbolicLink(path, formatToPath(destination))
+ } catch (exception: Exception) {
+ throw IOAccessException("Unable to create a new ${if (hard) "hard" else "symbolic"} link at '${unformatFromPath(path)}'", exception)
+ }
+
+ return this
+ }
+
+ /**
+ * Moves this file or directory.
+ *
+ * If you intend to rename a file or directory,
+ * this method handles renaming as well.
+ * They are 1:1 the same operation, so why
+ * create a whole new method just for that?
+ *
+ * If something already exists at the
+ * destination location, it will be forcefully
+ * deleted and the move operation performed.
+ *
+ * @param destination location where to move this file or directory to
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class)
+ fun move(destination: FileAccess): FileAccess {
+ try {
+ logger.diag("Moving '${unformatFromPath(path)}' to '${destination}")
+ Files.move(path, destination.path, StandardCopyOption.REPLACE_EXISTING)
+ return this
+ } catch (exception: Exception) {
+ throw IOAccessException("Unable to move '${unformatFromPath(path)}' to '${destination}'", exception)
+ }
+ }
+
+ /**
+ * Copies this file or directory.
+ *
+ * If something already exists at the
+ * destination location, it will be forcefully
+ * deleted and the copy operation performed.
+ *
+ * @param destination location where to copy this file or directory to
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class)
+ fun copy(destination: FileAccess): FileAccess {
+ try {
+ logger.diag("Copying '${unformatFromPath(path)}' to '${destination}")
+
+ if (file.isDirectory) {
+ destination.delete()
+ Files.walkFileTree(path, CopyDirectoryVisitor(path, destination.path))
+ } else
+ Files.copy(path, destination.path, StandardCopyOption.REPLACE_EXISTING)
+
+ return this
+ } catch (exception: Exception) {
+ throw IOAccessException("Unable to copy '${unformatFromPath(path)}' to '${destination}'", exception)
+ }
+ }
+
+ /**
+ * Deletes this file or directory.
+ *
+ * If nothing exists at this location,
+ * then nothing will be done.
+ *
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class)
+ fun delete(): FileAccess {
+ if (exists())
+ try {
+ logger.diag("Deleting '${unformatFromPath(path)}'")
+
+ if (file.isDirectory)
+ Files.walkFileTree(path, DeleteDirectoryVisitor(path))
+
+ Files.delete(path)
+ } catch (exception: Exception) {
+ throw IOAccessException("Unable to delete '${unformatFromPath(path)}'", exception)
+ }
+
+ return this
+ }
+
+ /**
+ * Marks this file for deletion at engine shutdown.
+ *
+ * @return this instance
+ * @see de.staropensource.engine.base.Engine.shutdown
+ * @since v1-alpha10
+ */
+ fun deleteOnShutdown(): FileAccess {
+ logger.diag("Marking '${path}' for deletion at engine shutdown")
+ scheduledDeletion.add(path)
+ return this
+ }
+
+
+ // -----> File access
+ /**
+ * Returns the contents of this file.
+ *
+ * @return file contents in bytes or `null` if not a file
+ * @throws IOAccessException on IO error
+ * @throws FileTooLargeException if the file is larger than the allocated amount of memory
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class, FileTooLargeException::class)
+ fun readBytes(): ByteArray? {
+ try {
+ if (getType() != Type.FILE)
+ return null
+
+ logger.diag("Reading from file '${path}' (bytes)")
+ return Files.readAllBytes(path)
+ } catch (error: OutOfMemoryError) {
+ throw FileTooLargeException(toString(), error)
+ } catch (exception: Exception) {
+ throw IOAccessException("Unable to read file '${path}' (bytes)", exception)
+ }
+ }
+
+ /**
+ * Returns the contents of this file.
+ *
+ * @return file contents in bytes or `null` if not a file
+ * @throws IOAccessException on IO error
+ * @throws FileTooLargeException if the file is larger than the allocated amount of memory
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class, FileTooLargeException::class)
+ fun readLines(): List? {
+ try {
+ if (getType() != Type.FILE)
+ return null
+
+ logger.diag("Reading from file '${path}' (lines)")
+ return Files.readAllLines(path)
+ } catch (error: OutOfMemoryError) {
+ throw FileTooLargeException(toString(), error)
+ } catch (exception: Exception) {
+ throw IOAccessException("Unable to read file '${path}' (lines)", exception)
+ }
+ }
+
+ /**
+ * Returns the contents of this file.
+ *
+ * @param charset character set used for string encoding
+ * @return file contents in bytes or `null` if not a file
+ * @throws IOAccessException on IO error
+ * @throws FileTooLargeException if the file is larger than the allocated amount of memory
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class, FileTooLargeException::class)
+ fun readString(): String? {
+ try {
+ if (getType() != Type.FILE)
+ return null
+
+ logger.diag("Reading from file '${path}' (string)")
+ return Files.readString(path)
+ } catch (error: OutOfMemoryError) {
+ throw FileTooLargeException(toString(), error)
+ } catch (exception: Exception) {
+ throw IOAccessException("Unable to read file '${path}' (string)", exception)
+ }
+ }
+
+ /**
+ * Writes the specified bytes into this file.
+ *
+ * A file is automatically created, if missing.
+ * The request is ignored if something exists
+ * at this location but is not a file.
+ *
+ * @param bytes bytes to write
+ * @param async allows the operating system to decide when to flush the file to disk if `true`, flushes the data to disk immediately if `false`
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @see verifyIsFile
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class)
+ fun writeBytes(bytes: ByteArray, async: Boolean = false): FileAccess {
+ try {
+ if (getType() != Type.FILE)
+ return this
+
+ logger.diag("Writing to file '${path}' (bytes, ${if (async) "async" else ""})")
+ createFile()
+ Files.write(path, bytes, StandardOpenOption.WRITE, if (async) StandardOpenOption.DSYNC else StandardOpenOption.SYNC)
+ } catch (exception: Exception) {
+ throw IOAccessException("Unable to read file '${path}' (bytes, ${if (async) "async" else ""})", exception)
+ }
+
+ return this
+ }
+
+ /**
+ * Writes the specified lines into this file.
+ *
+ * A file is automatically created, if missing.
+ * The request is ignored if something exists
+ * at this location but is not a file.
+ *
+ * @param lines lines to write
+ * @param async allows the operating system to decide when to flush the file to disk if `true`, flushes the data to disk immediately if `false`
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @see verifyIsFile
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class)
+ fun writeLines(lines: List, async: Boolean = false): FileAccess {
+ try {
+ if (getType() != Type.FILE)
+ return this
+
+ logger.diag("Writing to file '${path}' (lines, ${if (async) "async" else ""})")
+ createFile()
+ Files.write(path, lines, StandardOpenOption.WRITE, if (async) StandardOpenOption.DSYNC else StandardOpenOption.SYNC)
+ } catch (exception: Exception) {
+ throw IOAccessException("Unable to read file '${path}' (lines, ${if (async) "async" else ""})", exception)
+ }
+
+ return this
+ }
+
+ /**
+ * Writes the specified string into this file.
+ *
+ * A file is automatically created, if missing.
+ * The request is ignored if something exists
+ * at this location but is not a file.
+ *
+ * @param string string to write
+ * @param async allows the operating system to decide when to flush the file to disk if `true`, flushes the data to disk immediately if `false`
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @see verifyIsFile
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class)
+ fun writeString(string: String, async: Boolean = false): FileAccess {
+ try {
+ if (getType() != Type.FILE)
+ return this
+
+ logger.diag("Writing to file '${path}' (string, ${if (async) "async" else ""})")
+ createFile()
+ Files.writeString(path, string, StandardOpenOption.WRITE, if (async) StandardOpenOption.DSYNC else StandardOpenOption.SYNC)
+ } catch (exception: Exception) {
+ throw IOAccessException("Unable to read file '${path}' (string, ${if (async) "async" else ""})", exception)
+ }
+
+ return this
+ }
+
+ /**
+ * Appends the specified bytes into this file.
+ *
+ * A file is automatically created, if missing.
+ * The request is ignored if something exists
+ * at this location but is not a file.
+ *
+ * @param bytes bytes to append
+ * @param async allows the operating system to decide when to flush the file to disk if `true`, flushes the data to disk immediately if `false`
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @see verifyIsFile
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class)
+ fun appendBytes(bytes: ByteArray, async: Boolean = false): FileAccess {
+ try {
+ if (getType() != Type.FILE)
+ return this
+
+ logger.diag("Appending to file '${path}' (bytes, ${if (async) "async" else ""})")
+ createFile()
+ Files.write(path, bytes, StandardOpenOption.APPEND, if (async) StandardOpenOption.DSYNC else StandardOpenOption.SYNC)
+ } catch (exception: Exception) {
+ throw IOAccessException("Unable to read file '${path}' (bytes, ${if (async) "async" else ""})", exception)
+ }
+
+ return this
+ }
+
+ /**
+ * Appends the specified lines into this file.
+ *
+ * A file is automatically created, if missing.
+ * The request is ignored if something exists
+ * at this location but is not a file.
+ *
+ * @param lines lines to append
+ * @param async allows the operating system to decide when to flush the file to disk if `true`, flushes the data to disk immediately if `false`
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @see verifyIsFile
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class)
+ fun appendLines(lines: List, async: Boolean = false): FileAccess {
+ try {
+ if (getType() != Type.FILE)
+ return this
+
+ logger.diag("Appending to file '${path}' (lines, ${if (async) "async" else ""})")
+ createFile()
+ Files.write(path, lines, StandardOpenOption.APPEND, if (async) StandardOpenOption.DSYNC else StandardOpenOption.SYNC)
+ } catch (exception: Exception) {
+ throw IOAccessException("Unable to read file '${path}' (lines, ${if (async) "async" else ""})", exception)
+ }
+
+ return this
+ }
+
+ /**
+ * Appends the specified string into this file.
+ *
+ * A file is automatically created, if missing.
+ * The request is ignored if something exists
+ * at this location but is not a file.
+ *
+ * @param string string to append
+ * @param async allows the operating system to decide when to flush the file to disk if `true`, flushes the data to disk immediately if `false`
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @see verifyIsFile
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class)
+ fun appendString(string: String, async: Boolean = false): FileAccess {
+ try {
+ if (getType() != Type.FILE)
+ return this
+
+ logger.diag("Appending to file '${path}' (string, ${if (async) "async" else ""})")
+ createFile()
+ Files.writeString(path, string, StandardOpenOption.APPEND, if (async) StandardOpenOption.DSYNC else StandardOpenOption.SYNC)
+ } catch (exception: Exception) {
+ throw IOAccessException("Unable to read file '${path}' (string, ${if (async) "async" else ""})", exception)
+ }
+
+ return this
+ }
+
+
+ // -----> Directory access
+ /**
+ * Returns the names of all files and
+ * directories contained in this directory.
+ *
+ * @return array of file and directory names or `null` if not a directory
+ * @throws IOException on IO error
+ * @since v1-alpha10
+ */
+ fun list(): Array? {
+ if (getType() != Type.DIRECTORY)
+ return null
+
+ return file.list()
+ }
+
+ /**
+ * Returns the names of all files
+ * contained in this directory.
+ *
+ * @return array of file names or `null` if not a directory
+ * @throws IOException on IO error
+ * @since v1-alpha10
+ */
+ fun listFiles(): Array? {
+ if (getType() != Type.DIRECTORY)
+ return null
+
+ val listArray: Array? = file.list()
+ var list: MutableList = mutableListOf()
+
+ if (listArray == null)
+ throw IOException("list is 'null' (target is a directory)")
+
+ for (item: String in listArray)
+ if (path.resolve(item).toFile().isFile)
+ list.add(item)
+
+ return list.toTypedArray()
+ }
+
+ /**
+ * Returns the names of all directories
+ * contained in this directory.
+ *
+ * @return array of directory names or `null` if not a directory
+ * @throws IOException on IO error
+ * @since v1-alpha10
+ */
+ fun listDirectories(): Array? {
+ if (getType() != Type.DIRECTORY)
+ return null
+
+ val listArray: Array? = file.list()
+ var list: MutableList = mutableListOf()
+
+ if (listArray == null)
+ throw IOException("list is 'null' (target is a directory)")
+
+ for (item: String in listArray)
+ if (path.resolve(item).toFile().isDirectory)
+ list.add(item)
+
+ return list.toTypedArray()
+ }
+
+
+ // -----> Directory traversal
+ /**
+ * Returns the parent directory.
+ *
+ * @return [FileAccess] instance to the parent directory
+ * @since v1-alpha10
+ */
+ fun parent(): FileAccess {
+ return FileAccess(path.parent)
+ }
+
+ /**
+ * Traverses through directories and files.
+ *
+ * @param path path to traverse to
+ * @return new [FileAccess] instance
+ * @since v1-alpha10
+ */
+ fun traverse(path: String): FileAccess {
+ return FileAccess(this.path.resolve(formatToPath(path)))
+ }
+
+ /**
+ * Traverses through directories and files.
+ *
+ * Throws a [FileOrDirectoryNotFoundException]
+ * if the specific path cannot be accessed.
+ *
+ * @param path relative path to traverse to
+ * @return new [FileAccess] instance
+ * @throws FileOrDirectoryNotFoundException if the specific relative path does not exist
+ * @since v1-alpha10
+ */
+ @Throws(FileOrDirectoryNotFoundException::class)
+ fun traverseIfExists(path: String): FileAccess {
+ var pathResolved: Path = this.path.resolve(formatToPath(path))
+
+ if (!Files.exists(pathResolved))
+ throw FileOrDirectoryNotFoundException("Traversal failed as relative path '${path}' in absolute path '${unformatFromPath(this.path)}' does not exist")
+
+ return FileAccess(this.path.resolve(formatToPath(path)))
+ }
+
+
+ // -----> Verification
+ /**
+ * Verifies that something
+ * exists at this location.
+ *
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @throws VerificationFailedException if the verification fails
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class, VerificationFailedException::class)
+ fun verifyExists(): FileAccess {
+ if (!exists())
+ throw VerificationFailedException("Expected that something exists at '${unformatFromPath(path)}'")
+
+ return this
+ }
+
+ /**
+ * Verifies that something does
+ * not exist at this location.
+ *
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @throws VerificationFailedException if the verification fails
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class, VerificationFailedException::class)
+ fun verifyNotExists(): FileAccess {
+ if (exists())
+ throw VerificationFailedException("Expected that nothing exists at '${unformatFromPath(path)}'")
+
+ return this
+ }
+
+ /**
+ * Verifies that a file
+ * is at this location.
+ *
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @throws VerificationFailedException if the verification fails
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class, VerificationFailedException::class)
+ fun verifyIsFile(): FileAccess {
+ if (getType() != Type.FILE)
+ throw VerificationFailedException("Expected that '${unformatFromPath(path)}' is a file")
+
+ return this
+ }
+
+ /**
+ * Verifies that a file does
+ * not exist at this location.
+ *
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @throws VerificationFailedException if the verification fails
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class, VerificationFailedException::class)
+ fun verifyIsNotFile(): FileAccess {
+ if (getType() == Type.FILE)
+ throw VerificationFailedException("Expected that '${unformatFromPath(path)}' is not a file")
+
+ return this
+ }
+
+ /**
+ * Verifies that a directory
+ * is at this location.
+ *
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @throws VerificationFailedException if the verification fails
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class, VerificationFailedException::class)
+ fun verifyIsDirectory(): FileAccess {
+ if (getType() != Type.DIRECTORY)
+ throw VerificationFailedException("Expected that '${unformatFromPath(path)}' is a directory")
+
+ return this
+ }
+
+ /**
+ * Verifies that a directory does
+ * not exist at this location.
+ *
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @throws VerificationFailedException if the verification fails
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class, VerificationFailedException::class)
+ fun verifyIsNotDirectory(): FileAccess {
+ if (getType() == Type.DIRECTORY)
+ throw VerificationFailedException("Expected that '${unformatFromPath(path)}' is not a directory")
+
+ return this
+ }
+
+ /**
+ * Verifies that a link
+ * is at this location.
+ *
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @throws VerificationFailedException if the verification fails
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class, VerificationFailedException::class)
+ fun verifyIsLink(): FileAccess {
+ if (exists() && (isSymbolicLink() || getLinkDestination() != null))
+ throw VerificationFailedException("Expected that '${unformatFromPath(path)}' is a link")
+
+ return this
+ }
+
+ /**
+ * Verifies that a link does
+ * not exist at this location.
+ *
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @throws VerificationFailedException if the verification fails
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class, VerificationFailedException::class)
+ fun verifyIsNotLink(): FileAccess {
+ if (exists() && !(isSymbolicLink() || getLinkDestination() != null))
+ throw VerificationFailedException("Expected that '${unformatFromPath(path)}' is not a link")
+
+ return this
+ }
+
+ /**
+ * Verifies that a symbolic
+ * link is at this location.
+ *
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @throws VerificationFailedException if the verification fails
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class, VerificationFailedException::class)
+ fun verifyIsSymbolicLink(): FileAccess {
+ if (!isSymbolicLink())
+ throw VerificationFailedException("Expected that '${unformatFromPath(path)}' is a symbolic link")
+
+ return this
+ }
+
+ /**
+ * Verifies that a symbolic link
+ * does not exist at this location.
+ *
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @throws VerificationFailedException if the verification fails
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class, VerificationFailedException::class)
+ fun verifyIsNotSymbolicLink(): FileAccess {
+ if (isSymbolicLink())
+ throw VerificationFailedException("Expected that '${unformatFromPath(path)}' is not a symbolic link")
+
+ return this
+ }
+
+ /**
+ * Verifies that a symbolic
+ * link is at this location.
+ *
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @throws VerificationFailedException if the verification fails
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class, VerificationFailedException::class)
+ fun verifyIsHardLink(): FileAccess {
+ if (exists() && !isSymbolicLink() && getLinkDestination() != null)
+ throw VerificationFailedException("Expected that '${unformatFromPath(path)}' is a hard link")
+
+ return this
+ }
+
+ /**
+ * Verifies that a symbolic link
+ * does not exist at this location.
+ *
+ * @return this instance
+ * @throws IOAccessException on IO error
+ * @throws VerificationFailedException if the verification fails
+ * @since v1-alpha10
+ */
+ @Throws(IOAccessException::class, VerificationFailedException::class)
+ fun verifyIsNotHardLink(): FileAccess {
+ if (exists() && !isSymbolicLink() && getLinkDestination() == null)
+ throw VerificationFailedException("Expected that '${unformatFromPath(path)}' is not a hard link")
+
+ return this
+ }
+
+
+ // -----> Inner classes
+ /**
+ * Represents various types of files.
+ *
+ * @since v1-alpha8
+ */
+ enum class Type {
+ /**
+ * Identifies that the path does not exist.
+ *
+ * @since v1-alpha8
+ */
+ VOID,
+
+ /**
+ * Identifies that the path is a regular file.
+ *
+ * @since v1-alpha8
+ */
+ FILE,
+
+ /**
+ * Identifies that the path is a directory.
+ *
+ * @since v1-alpha8
+ */
+ DIRECTORY,
+
+ /**
+ * Identifies that the path is unknown
+ * to the StarOpenSource Engine.
+ *
+ * @since v1-alpha8
+ */
+ UNKNOWN
+ }
+
+ /**
+ * {@link FileVisitor} instance for
+ * copying directories recursively.
+ *
+ * @param source source to copy from
+ * @param destination destination to copy to
+ * @since v1-alpha9
+ */
+ private class CopyDirectoryVisitor(val source: Path, val destination: Path) : FileVisitor {
+ override fun preVisitDirectory(path: Path, attributes: BasicFileAttributes): FileVisitResult {
+ Files.createDirectories(destination.resolve(source.relativize(path)))
+ return FileVisitResult.CONTINUE
+ }
+
+ override fun visitFile(path: Path, attributes: BasicFileAttributes): FileVisitResult {
+ Files.copy(path, destination.resolve(source.relativize(path)))
+ return FileVisitResult.CONTINUE
+ }
+
+ override fun visitFileFailed(path: Path, exception: IOException): FileVisitResult {
+ throw exception
+ }
+
+ override fun postVisitDirectory(path: Path, exception: IOException?): FileVisitResult {
+ if (exception != null)
+ throw exception
+
+ return FileVisitResult.CONTINUE
+ }
+ }
+
+ /**
+ * {@link FileVisitor} instance for
+ * delete directories recursively.
+ *
+ * @param directory directory to delete
+ * @since v1-alpha9
+ */
+ private class DeleteDirectoryVisitor(directory: Path) : FileVisitor {
+ override fun preVisitDirectory(path: Path, attributes: BasicFileAttributes): FileVisitResult {
+ return FileVisitResult.CONTINUE
+ }
+
+ override fun visitFile(path: Path, attributes: BasicFileAttributes): FileVisitResult {
+ return FileVisitResult.CONTINUE
+ }
+
+ override fun visitFileFailed(path: Path, exception: IOException): FileVisitResult {
+ throw exception
+ }
+
+ override fun postVisitDirectory(path: Path, exception: IOException?): FileVisitResult {
+ if (exception != null)
+ throw exception
+
+ Files.delete(path)
+ return FileVisitResult.CONTINUE
+ }
+ }
+}
diff --git a/dist/detekt.yml b/dist/detekt.yml
index 4fc3eed..95c5c1f 100644
--- a/dist/detekt.yml
+++ b/dist/detekt.yml
@@ -18,6 +18,10 @@ build:
complexity:
TooManyFunctions:
active: false
+ NestedBlockDepth:
+ threshold: 6
+ ComplexCondition:
+ active: false
naming:
MemberNameEqualsClassName:
@@ -38,3 +42,7 @@ style:
active: false
UnusedParameter:
active: false
+ ReturnCount:
+ active: false
+ WildcardImport:
+ active: false