Home | History | Annotate | Download | only in backup
      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