aboutsummaryrefslogtreecommitdiff
path: root/okio/src/commonMain/kotlin/okio/internal/FileSystem.kt
diff options
context:
space:
mode:
Diffstat (limited to 'okio/src/commonMain/kotlin/okio/internal/FileSystem.kt')
-rw-r--r--okio/src/commonMain/kotlin/okio/internal/FileSystem.kt154
1 files changed, 154 insertions, 0 deletions
diff --git a/okio/src/commonMain/kotlin/okio/internal/FileSystem.kt b/okio/src/commonMain/kotlin/okio/internal/FileSystem.kt
new file mode 100644
index 00000000..72c541d1
--- /dev/null
+++ b/okio/src/commonMain/kotlin/okio/internal/FileSystem.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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.
+ */
+@file:JvmName("-FileSystem") // A leading '-' hides this class from Java.
+
+package okio.internal
+
+import kotlin.jvm.JvmName
+import okio.FileMetadata
+import okio.FileNotFoundException
+import okio.FileSystem
+import okio.IOException
+import okio.Path
+import okio.buffer
+import okio.use
+
+/**
+ * Returns metadata of the file, directory, or object identified by [path].
+ *
+ * @throws IOException if [path] does not exist or its metadata cannot be read.
+ */
+@Throws(IOException::class)
+internal fun FileSystem.commonMetadata(path: Path): FileMetadata {
+ return metadataOrNull(path) ?: throw FileNotFoundException("no such file: $path")
+}
+
+@Throws(IOException::class)
+internal fun FileSystem.commonExists(path: Path): Boolean {
+ return metadataOrNull(path) != null
+}
+
+@Throws(IOException::class)
+internal fun FileSystem.commonCreateDirectories(dir: Path, mustCreate: Boolean) {
+ // Compute the sequence of directories to create.
+ val directories = ArrayDeque<Path>()
+ var path: Path? = dir
+ while (path != null && !exists(path)) {
+ directories.addFirst(path)
+ path = path.parent
+ }
+
+ if (mustCreate && directories.isEmpty()) throw IOException("$dir already exists.")
+
+ // Create them.
+ for (toCreate in directories) {
+ // We know we are creating new directories by now so we don't have to pass down `mustCreate`.
+ createDirectory(toCreate)
+ }
+}
+
+@Throws(IOException::class)
+internal fun FileSystem.commonCopy(source: Path, target: Path) {
+ source(source).use { bytesIn ->
+ sink(target).buffer().use { bytesOut ->
+ bytesOut.writeAll(bytesIn)
+ }
+ }
+}
+
+@Throws(IOException::class)
+internal fun FileSystem.commonDeleteRecursively(fileOrDirectory: Path, mustExist: Boolean) {
+ val sequence = sequence {
+ collectRecursively(
+ fileSystem = this@commonDeleteRecursively,
+ stack = ArrayDeque(),
+ path = fileOrDirectory,
+ followSymlinks = false,
+ postorder = true,
+ )
+ }
+ val iterator = sequence.iterator()
+ while (iterator.hasNext()) {
+ val toDelete = iterator.next()
+ delete(toDelete, mustExist = mustExist && !iterator.hasNext())
+ }
+}
+
+@Throws(IOException::class)
+internal fun FileSystem.commonListRecursively(dir: Path, followSymlinks: Boolean): Sequence<Path> {
+ return sequence {
+ val stack = ArrayDeque<Path>()
+ stack.addLast(dir)
+ for (child in list(dir)) {
+ collectRecursively(
+ fileSystem = this@commonListRecursively,
+ stack = stack,
+ path = child,
+ followSymlinks = followSymlinks,
+ postorder = false,
+ )
+ }
+ }
+}
+
+internal suspend fun SequenceScope<Path>.collectRecursively(
+ fileSystem: FileSystem,
+ stack: ArrayDeque<Path>,
+ path: Path,
+ followSymlinks: Boolean,
+ postorder: Boolean,
+) {
+ // For listRecursively, visit enclosing directory first.
+ if (!postorder) {
+ yield(path)
+ }
+
+ val children = fileSystem.listOrNull(path) ?: listOf()
+ if (children.isNotEmpty()) {
+ // Figure out if path is a symlink and detect symlink cycles.
+ var symlinkPath = path
+ var symlinkCount = 0
+ while (true) {
+ if (followSymlinks && symlinkPath in stack) throw IOException("symlink cycle at $path")
+ symlinkPath = fileSystem.symlinkTarget(symlinkPath) ?: break
+ symlinkCount++
+ }
+
+ // Recursively visit children.
+ if (followSymlinks || symlinkCount == 0) {
+ stack.addLast(symlinkPath)
+ try {
+ for (child in children) {
+ collectRecursively(fileSystem, stack, child, followSymlinks, postorder)
+ }
+ } finally {
+ stack.removeLast()
+ }
+ }
+ }
+
+ // For deleteRecursively, visit enclosing directory last.
+ if (postorder) {
+ yield(path)
+ }
+}
+
+/** Returns a resolved path to the symlink target, resolving it if necessary. */
+@Throws(IOException::class)
+internal fun FileSystem.symlinkTarget(path: Path): Path? {
+ val target = metadata(path).symlinkTarget ?: return null
+ return path.parent!!.div(target)
+}