1 /* 2 * Copyright (C) 2017 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.server.backup; 18 19 import android.annotation.Nullable; 20 21 import java.io.BufferedInputStream; 22 import java.io.DataInputStream; 23 import java.io.File; 24 import java.io.FileInputStream; 25 import java.io.IOException; 26 import java.io.RandomAccessFile; 27 import java.util.ArrayList; 28 import java.util.List; 29 import java.util.function.Consumer; 30 31 /** 32 * A journal of packages that have indicated that their data has changed (and therefore should be 33 * backed up in the next scheduled K/V backup pass). 34 * 35 * <p>This information is persisted to the filesystem so that it is not lost in the event of a 36 * reboot. 37 */ 38 public class DataChangedJournal { 39 private static final String FILE_NAME_PREFIX = "journal"; 40 41 /** 42 * Journals tend to be on the order of a few kilobytes, hence setting the buffer size to 8kb. 43 */ 44 private static final int BUFFER_SIZE_BYTES = 8 * 1024; 45 46 private final File mFile; 47 48 /** 49 * Constructs an instance that reads from and writes to the given file. 50 */ DataChangedJournal(File file)51 DataChangedJournal(File file) { 52 mFile = file; 53 } 54 55 /** 56 * Adds the given package to the journal. 57 * 58 * @param packageName The name of the package whose data has changed. 59 * @throws IOException if there is an IO error writing to the journal file. 60 */ addPackage(String packageName)61 public void addPackage(String packageName) throws IOException { 62 try (RandomAccessFile out = new RandomAccessFile(mFile, "rws")) { 63 out.seek(out.length()); 64 out.writeUTF(packageName); 65 } 66 } 67 68 /** 69 * Invokes {@link Consumer#accept(Object)} with every package name in the journal file. 70 * 71 * @param consumer The callback. 72 * @throws IOException If there is an IO error reading from the file. 73 */ forEach(Consumer<String> consumer)74 public void forEach(Consumer<String> consumer) throws IOException { 75 try ( 76 BufferedInputStream bufferedInputStream = new BufferedInputStream( 77 new FileInputStream(mFile), BUFFER_SIZE_BYTES); 78 DataInputStream dataInputStream = new DataInputStream(bufferedInputStream) 79 ) { 80 while (dataInputStream.available() > 0) { 81 String packageName = dataInputStream.readUTF(); 82 consumer.accept(packageName); 83 } 84 } 85 } 86 87 /** 88 * Returns a list with the packages in this journal. 89 * 90 * @throws IOException If there is an IO error reading from the file. 91 */ getPackages()92 public List<String> getPackages() throws IOException { 93 List<String> packages = new ArrayList<>(); 94 forEach(packages::add); 95 return packages; 96 } 97 98 /** 99 * Deletes the journal from the filesystem. 100 * 101 * @return {@code true} if successfully deleted journal. 102 */ delete()103 public boolean delete() { 104 return mFile.delete(); 105 } 106 107 @Override equals(@ullable Object object)108 public boolean equals(@Nullable Object object) { 109 if (object instanceof DataChangedJournal) { 110 DataChangedJournal that = (DataChangedJournal) object; 111 try { 112 return this.mFile.getCanonicalPath().equals(that.mFile.getCanonicalPath()); 113 } catch (IOException exception) { 114 return false; 115 } 116 } 117 return false; 118 } 119 120 @Override toString()121 public String toString() { 122 return mFile.toString(); 123 } 124 125 /** 126 * Creates a new journal with a random file name in the given journal directory. 127 * 128 * @param journalDirectory The directory where journals are kept. 129 * @return The journal. 130 * @throws IOException if there is an IO error creating the file. 131 */ newJournal(File journalDirectory)132 static DataChangedJournal newJournal(File journalDirectory) throws IOException { 133 return new DataChangedJournal( 134 File.createTempFile(FILE_NAME_PREFIX, null, journalDirectory)); 135 } 136 137 /** 138 * Returns a list of journals in the given journal directory. 139 */ listJournals(File journalDirectory)140 static ArrayList<DataChangedJournal> listJournals(File journalDirectory) { 141 ArrayList<DataChangedJournal> journals = new ArrayList<>(); 142 for (File file : journalDirectory.listFiles()) { 143 journals.add(new DataChangedJournal(file)); 144 } 145 return journals; 146 } 147 } 148