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