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