1 /* 2 * Copyright 2022 Google LLC 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 com.google.android.libraries.mobiledatadownload.file.backends; 17 18 import android.net.Uri; 19 import android.os.ParcelFileDescriptor; 20 import android.util.Pair; 21 import com.google.android.libraries.mobiledatadownload.file.common.LockScope; 22 import com.google.android.libraries.mobiledatadownload.file.common.internal.BackendInputStream; 23 import com.google.android.libraries.mobiledatadownload.file.common.internal.BackendOutputStream; 24 import com.google.android.libraries.mobiledatadownload.file.spi.Backend; 25 import com.google.common.io.Files; 26 import java.io.Closeable; 27 import java.io.File; 28 import java.io.FileNotFoundException; 29 import java.io.IOException; 30 import java.io.InputStream; 31 import java.io.OutputStream; 32 import java.util.ArrayList; 33 import java.util.List; 34 35 /** A backend that implements "file:" scheme using java.io.FileInput/OutputStream. */ 36 public final class JavaFileBackend implements Backend { 37 38 private final LockScope lockScope; 39 JavaFileBackend()40 public JavaFileBackend() { 41 this(new LockScope()); 42 } 43 44 /** 45 * Overrides the default backend-scoped {@link LockScope} with the given {@code lockScope}. This 46 * injection is only necessary if there are multiple backend instances in the same process and 47 * there's a risk of them acquiring a lock on the same underlying file. 48 */ JavaFileBackend(LockScope lockScope)49 public JavaFileBackend(LockScope lockScope) { 50 this.lockScope = lockScope; 51 } 52 53 @Override name()54 public String name() { 55 return "file"; 56 } 57 58 @Override openForRead(Uri uri)59 public InputStream openForRead(Uri uri) throws IOException { 60 File file = FileUriAdapter.instance().toFile(uri); 61 return BackendInputStream.create(file); 62 } 63 64 @Override openForNativeRead(Uri uri)65 public Pair<Uri, Closeable> openForNativeRead(Uri uri) throws IOException { 66 File file = FileUriAdapter.instance().toFile(uri); 67 ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); 68 return FileDescriptorUri.fromParcelFileDescriptor(pfd); 69 } 70 71 @Override openForWrite(Uri uri)72 public OutputStream openForWrite(Uri uri) throws IOException { 73 File file = FileUriAdapter.instance().toFile(uri); 74 Files.createParentDirs(file); 75 return BackendOutputStream.createForWrite(file); 76 } 77 78 @Override openForAppend(Uri uri)79 public OutputStream openForAppend(Uri uri) throws IOException { 80 File file = FileUriAdapter.instance().toFile(uri); 81 Files.createParentDirs(file); 82 return BackendOutputStream.createForAppend(file); 83 } 84 85 @Override deleteFile(Uri uri)86 public void deleteFile(Uri uri) throws IOException { 87 File file = FileUriAdapter.instance().toFile(uri); 88 if (file.isDirectory()) { 89 throw new FileNotFoundException(String.format("%s is a directory", uri)); 90 } 91 if (!file.delete()) { 92 if (!file.exists()) { 93 throw new FileNotFoundException(String.format("%s does not exist", uri)); 94 } else { 95 throw new IOException(String.format("%s could not be deleted", uri)); 96 } 97 } 98 } 99 100 @Override deleteDirectory(Uri uri)101 public void deleteDirectory(Uri uri) throws IOException { 102 File dir = FileUriAdapter.instance().toFile(uri); 103 if (!dir.isDirectory()) { 104 throw new FileNotFoundException(String.format("%s is not a directory", uri)); 105 } 106 if (!dir.delete()) { 107 throw new IOException(String.format("%s could not be deleted", uri)); 108 } 109 } 110 111 @Override rename(Uri from, Uri to)112 public void rename(Uri from, Uri to) throws IOException { 113 File fromFile = FileUriAdapter.instance().toFile(from); 114 File toFile = FileUriAdapter.instance().toFile(to); 115 Files.createParentDirs(toFile); 116 if (!fromFile.renameTo(toFile)) { 117 throw new IOException(String.format("%s could not be renamed to %s", from, to)); 118 } 119 } 120 121 @Override exists(Uri uri)122 public boolean exists(Uri uri) throws IOException { 123 File file = FileUriAdapter.instance().toFile(uri); 124 return file.exists(); 125 } 126 127 @Override isDirectory(Uri uri)128 public boolean isDirectory(Uri uri) throws IOException { 129 File file = FileUriAdapter.instance().toFile(uri); 130 return file.isDirectory(); 131 } 132 133 @Override createDirectory(Uri uri)134 public void createDirectory(Uri uri) throws IOException { 135 File file = FileUriAdapter.instance().toFile(uri); 136 if (!file.mkdirs()) { 137 throw new IOException(String.format("%s could not be created", uri)); 138 } 139 } 140 141 @Override fileSize(Uri uri)142 public long fileSize(Uri uri) throws IOException { 143 File file = FileUriAdapter.instance().toFile(uri); 144 if (file.isDirectory()) { 145 return 0; 146 } 147 return file.length(); 148 } 149 150 @Override children(Uri parentUri)151 public Iterable<Uri> children(Uri parentUri) throws IOException { 152 File parent = FileUriAdapter.instance().toFile(parentUri); 153 if (!parent.isDirectory()) { 154 throw new FileNotFoundException(String.format("%s is not a directory", parentUri)); 155 } 156 File[] children = parent.listFiles(); 157 if (children == null) { 158 throw new IOException( 159 String.format("Not a directory or I/O error (unexpected): %s", parentUri)); 160 } 161 List<Uri> result = new ArrayList<Uri>(); 162 for (File child : children) { 163 String path = child.getAbsolutePath(); 164 if (child.isDirectory() && !path.endsWith("/")) { 165 path += "/"; 166 } 167 result.add(FileUri.builder().setPath(path).build()); 168 } 169 return result; 170 } 171 172 @Override toFile(Uri uri)173 public File toFile(Uri uri) throws IOException { 174 return FileUriAdapter.instance().toFile(uri); 175 } 176 177 @Override lockScope()178 public LockScope lockScope() throws IOException { 179 return lockScope; 180 } 181 } 182