xref: /aosp_15_r20/external/okio/okio/src/jvmMain/kotlin/okio/JvmSystemFileSystem.kt (revision f9742813c14b702d71392179818a9e591da8620c)
1 /*
2  * Copyright (C) 2020 Square, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package okio
17 
18 import java.io.InterruptedIOException
19 import java.io.RandomAccessFile
20 import okio.Path.Companion.toOkioPath
21 
22 /**
23  * A file system that adapts `java.io`.
24  *
25  * This base class is used on Android API levels 15 (our minimum supported API) through 26
26  * (the first release that includes java.nio.file).
27  */
28 internal open class JvmSystemFileSystem : FileSystem() {
canonicalizenull29   override fun canonicalize(path: Path): Path {
30     val canonicalFile = path.toFile().canonicalFile
31     if (!canonicalFile.exists()) throw FileNotFoundException("no such file")
32     return canonicalFile.toOkioPath()
33   }
34 
metadataOrNullnull35   override fun metadataOrNull(path: Path): FileMetadata? {
36     val file = path.toFile()
37     val isRegularFile = file.isFile
38     val isDirectory = file.isDirectory
39     val lastModifiedAtMillis = file.lastModified()
40     val size = file.length()
41 
42     if (!isRegularFile &&
43       !isDirectory &&
44       lastModifiedAtMillis == 0L &&
45       size == 0L &&
46       !file.exists()
47     ) {
48       return null
49     }
50 
51     return FileMetadata(
52       isRegularFile = isRegularFile,
53       isDirectory = isDirectory,
54       symlinkTarget = null,
55       size = size,
56       createdAtMillis = null,
57       lastModifiedAtMillis = lastModifiedAtMillis,
58       lastAccessedAtMillis = null,
59     )
60   }
61 
listnull62   override fun list(dir: Path): List<Path> = list(dir, throwOnFailure = true)!!
63 
64   override fun listOrNull(dir: Path): List<Path>? = list(dir, throwOnFailure = false)
65 
66   private fun list(dir: Path, throwOnFailure: Boolean): List<Path>? {
67     val file = dir.toFile()
68     val entries = file.list()
69     if (entries == null) {
70       if (throwOnFailure) {
71         if (!file.exists()) throw FileNotFoundException("no such file: $dir")
72         throw IOException("failed to list $dir")
73       } else {
74         return null
75       }
76     }
77     val result = entries.mapTo(mutableListOf()) { dir / it }
78     result.sort()
79     return result
80   }
81 
openReadOnlynull82   override fun openReadOnly(file: Path): FileHandle {
83     return JvmFileHandle(readWrite = false, randomAccessFile = RandomAccessFile(file.toFile(), "r"))
84   }
85 
openReadWritenull86   override fun openReadWrite(file: Path, mustCreate: Boolean, mustExist: Boolean): FileHandle {
87     require(!mustCreate || !mustExist) {
88       "Cannot require mustCreate and mustExist at the same time."
89     }
90     if (mustCreate) file.requireCreate()
91     if (mustExist) file.requireExist()
92     return JvmFileHandle(readWrite = true, randomAccessFile = RandomAccessFile(file.toFile(), "rw"))
93   }
94 
sourcenull95   override fun source(file: Path): Source {
96     return file.toFile().source()
97   }
98 
sinknull99   override fun sink(file: Path, mustCreate: Boolean): Sink {
100     if (mustCreate) file.requireCreate()
101     return file.toFile().sink()
102   }
103 
appendingSinknull104   override fun appendingSink(file: Path, mustExist: Boolean): Sink {
105     if (mustExist) file.requireExist()
106     return file.toFile().sink(append = true)
107   }
108 
createDirectorynull109   override fun createDirectory(dir: Path, mustCreate: Boolean) {
110     if (!dir.toFile().mkdir()) {
111       val alreadyExist = metadataOrNull(dir)?.isDirectory == true
112       if (alreadyExist) {
113         if (mustCreate) {
114           throw IOException("$dir already exists.")
115         } else {
116           return
117         }
118       }
119       throw IOException("failed to create directory: $dir")
120     }
121   }
122 
atomicMovenull123   override fun atomicMove(source: Path, target: Path) {
124     // Note that on Windows, this will fail if [target] already exists.
125     val renamed = source.toFile().renameTo(target.toFile())
126     if (!renamed) throw IOException("failed to move $source to $target")
127   }
128 
deletenull129   override fun delete(path: Path, mustExist: Boolean) {
130     if (Thread.interrupted()) {
131       // If the current thread has been interrupted.
132       throw InterruptedIOException("interrupted")
133     }
134     val file = path.toFile()
135     val deleted = file.delete()
136     if (!deleted) {
137       if (file.exists()) throw IOException("failed to delete $path")
138       if (mustExist) throw FileNotFoundException("no such file: $path")
139     }
140   }
141 
createSymlinknull142   override fun createSymlink(source: Path, target: Path) {
143     throw IOException("unsupported")
144   }
145 
toStringnull146   override fun toString() = "JvmSystemFileSystem"
147 
148   // We have to implement existence verification non-atomically on the JVM because there's no API
149   // to do so.
150   private fun Path.requireExist() {
151     if (!exists(this)) throw IOException("$this doesn't exist.")
152   }
153 
Pathnull154   private fun Path.requireCreate() {
155     if (exists(this)) throw IOException("$this already exists.")
156   }
157 }
158