1 /* 2 * Copyright (C) 2021 The Android Open Source Project 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 17 package com.android.car.telemetry.util; 18 19 import android.annotation.NonNull; 20 import android.car.builtin.util.Slogf; 21 import android.os.PersistableBundle; 22 import android.util.AtomicFile; 23 24 import com.android.car.CarLog; 25 26 import com.google.protobuf.MessageLite; 27 28 import java.io.Closeable; 29 import java.io.File; 30 import java.io.FileInputStream; 31 import java.io.FileOutputStream; 32 import java.io.IOException; 33 import java.nio.file.Files; 34 import java.nio.file.Paths; 35 36 /** Utility class for car telemetry I/O operations. */ 37 public class IoUtils { 38 39 /** 40 * Reads a {@link PersistableBundle} from the file system. 41 * 42 * @param bundleFile file location of the PersistableBundle. 43 * @return {@link PersistableBundle} stored in the given file. 44 * @throws IOException for read failure. 45 */ 46 @NonNull readBundle(@onNull File bundleFile)47 public static PersistableBundle readBundle(@NonNull File bundleFile) throws IOException { 48 AtomicFile atomicFile = new AtomicFile(bundleFile); 49 try (FileInputStream fis = atomicFile.openRead()) { 50 return PersistableBundle.readFromStream(fis); 51 } 52 } 53 54 /** 55 * Saves a {@link PersistableBundle} to a file. 56 * 57 * @param dir directory to save the bundle to. 58 * @param fileName file name to save the file as. 59 * @param bundle to be saved. 60 * @throws IOException for write failure. 61 */ writeBundle( @onNull File dir, @NonNull String fileName, @NonNull PersistableBundle bundle)62 public static void writeBundle( 63 @NonNull File dir, @NonNull String fileName, @NonNull PersistableBundle bundle) 64 throws IOException { 65 writeBundle(new File(dir, fileName), bundle); 66 } 67 68 /** 69 * Saves a {@link PersistableBundle} to a file. 70 * 71 * @param dest file location to save the {@link PersistableBundle}. 72 * @param bundle to be saved. 73 * @throws IOException for write failure. 74 */ writeBundle(@onNull File dest, @NonNull PersistableBundle bundle)75 public static void writeBundle(@NonNull File dest, @NonNull PersistableBundle bundle) 76 throws IOException { 77 AtomicFile atomicFile = new AtomicFile(dest); 78 try (FileOutputStream fos = atomicFile.startWrite()) { 79 try { 80 bundle.writeToStream(fos); 81 atomicFile.finishWrite(fos); 82 } catch (IOException e) { 83 atomicFile.failWrite(fos); 84 throw e; 85 } 86 } 87 } 88 89 /** 90 * Saves a protobuf message to file. 91 * 92 * @param dir directory to save the file to. 93 * @param fileName name to save the file as. 94 * @param proto to be saved. 95 * @throws IOException for write failure. 96 */ writeProto( @onNull File dir, @NonNull String fileName, @NonNull MessageLite proto)97 public static void writeProto( 98 @NonNull File dir, @NonNull String fileName, @NonNull MessageLite proto) 99 throws IOException { 100 writeProto(new File(dir, fileName), proto); 101 } 102 103 /** 104 * Savea protobuf message to file. 105 * 106 * @param dest file location to save the protobuf message. 107 * @param proto to be saved. 108 * @throws IOException for write failure. 109 */ writeProto( @onNull File dest, @NonNull MessageLite proto)110 public static void writeProto( 111 @NonNull File dest, @NonNull MessageLite proto) throws IOException { 112 AtomicFile atomicFile = new AtomicFile(dest); 113 try (FileOutputStream fos = atomicFile.startWrite()) { 114 try { 115 fos.write(proto.toByteArray()); 116 atomicFile.finishWrite(fos); 117 } catch (IOException e) { 118 atomicFile.failWrite(fos); 119 throw e; 120 } 121 } 122 } 123 124 /** 125 * Deletes the file silently from the file system if it exists. Return true for success, false 126 * for failure. 127 */ deleteSilently(@onNull File directory, @NonNull String fileName)128 public static boolean deleteSilently(@NonNull File directory, @NonNull String fileName) { 129 try { 130 return Files.deleteIfExists(Paths.get( 131 directory.getAbsolutePath(), fileName)); 132 } catch (IOException e) { 133 Slogf.w(CarLog.TAG_TELEMETRY, "Failed to delete file " + fileName + " in directory " 134 + directory.getAbsolutePath(), e); 135 // TODO(b/197153560): record failure 136 } 137 return false; 138 } 139 140 /** 141 * Deletes all files silently from the directory. This method does not delete recursively. 142 */ deleteAllSilently(@onNull File directory)143 public static void deleteAllSilently(@NonNull File directory) { 144 File[] files = directory.listFiles(); 145 if (files == null) { 146 Slogf.i(CarLog.TAG_TELEMETRY, "Skip deleting the empty dir %s", directory.getName()); 147 return; 148 } 149 for (File file : files) { 150 if (!file.delete()) { 151 Slogf.w(CarLog.TAG_TELEMETRY, "Failed to delete file " + file.getName() 152 + " in directory " + directory.getAbsolutePath()); 153 } 154 } 155 } 156 157 /** 158 * Deletes all files in the specified directories that are stale/older than some threshold. 159 * This method does not delete recursively. 160 * 161 * @param staleThresholdMillis the threshold to classify a file as stale. 162 * @param dirs the directories to remove stale files from. 163 */ deleteOldFiles(long staleThresholdMillis, @NonNull File... dirs)164 public static void deleteOldFiles(long staleThresholdMillis, @NonNull File... dirs) { 165 long currTimeMs = System.currentTimeMillis(); 166 for (File dir : dirs) { 167 File[] files = dir.listFiles(); 168 if (files == null) { 169 Slogf.i(CarLog.TAG_TELEMETRY, "Skip deleting the empty dir %s", dir.getName()); 170 continue; 171 } 172 for (File file : files) { 173 // delete stale data 174 if (file.lastModified() + staleThresholdMillis < currTimeMs) { 175 file.delete(); 176 } 177 } 178 } 179 } 180 181 /** Quietly closes Java Closeables, ignoring IOException. */ closeQuietly(@onNull Closeable closeable)182 public static void closeQuietly(@NonNull Closeable closeable) { 183 try { 184 closeable.close(); 185 } catch (IOException e) { 186 // Ignore 187 } 188 } 189 } 190