Journal backup requests so that they won't be lost in a crash

When an application requests a backup via dataChanged(), we now journal that
fact on disk.  The journal persists and is only removed following a successful
backup pass.  When the backup manager is started at boot time, it looks for any
existing journal files and schedules a backup for the apps listed in them, on
the expectation that the device shut down or crashed before a backup could be
performed.
This commit is contained in:
Christopher Tate
2009-06-12 12:55:53 -07:00
parent 0b77453076
commit cde87f45e0

View File

@@ -51,11 +51,13 @@ import com.android.internal.backup.LocalTransport;
import com.android.internal.backup.GoogleTransport;
import com.android.internal.backup.IBackupTransport;
import java.io.EOFException;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.lang.String;
import java.util.ArrayList;
import java.util.HashMap;
@@ -122,6 +124,9 @@ class BackupManagerService extends IBackupManager.Stub {
private File mStateDir;
private File mDataDir;
private File mJournalDir;
private File mJournal;
private RandomAccessFile mJournalStream;
public BackupManagerService(Context context) {
mContext = context;
@@ -133,6 +138,11 @@ class BackupManagerService extends IBackupManager.Stub {
mStateDir.mkdirs();
mDataDir = Environment.getDownloadCacheDirectory();
// Set up the backup-request journaling
mJournalDir = new File(mStateDir, "pending");
mJournalDir.mkdirs();
makeJournalLocked(); // okay because no other threads are running yet
//!!! TODO: default to cloud transport, not local
mTransportId = BackupManager.TRANSPORT_LOCAL;
@@ -141,6 +151,10 @@ class BackupManagerService extends IBackupManager.Stub {
addPackageParticipantsLocked(null);
}
// Now that we know about valid backup participants, parse any
// leftover journal files and schedule a new backup pass
parseLeftoverJournals();
// Register for broadcasts about package install, etc., so we can
// update the provider list.
IntentFilter filter = new IntentFilter();
@@ -150,6 +164,46 @@ class BackupManagerService extends IBackupManager.Stub {
mContext.registerReceiver(mBroadcastReceiver, filter);
}
private void makeJournalLocked() {
try {
mJournal = File.createTempFile("journal", null, mJournalDir);
mJournalStream = new RandomAccessFile(mJournal, "rwd");
} catch (IOException e) {
Log.e(TAG, "Unable to write backup journals");
mJournal = null;
mJournalStream = null;
}
}
private void parseLeftoverJournals() {
if (mJournal != null) {
File[] allJournals = mJournalDir.listFiles();
for (File f : allJournals) {
if (f.compareTo(mJournal) != 0) {
// This isn't the current journal, so it must be a leftover. Read
// out the package names mentioned there and schedule them for
// backup.
try {
Log.i(TAG, "Found stale backup journal, scheduling:");
RandomAccessFile in = new RandomAccessFile(f, "r");
while (true) {
String packageName = in.readUTF();
Log.i(TAG, " + " + packageName);
dataChanged(packageName);
}
} catch (EOFException e) {
// no more data; we're done
} catch (Exception e) {
// can't read it or other error; just skip it
} finally {
// close/delete the file
f.delete();
}
}
}
}
}
// ----- Track installation/removal of packages -----
BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
@@ -198,6 +252,8 @@ class BackupManagerService extends IBackupManager.Stub {
switch (msg.what) {
case MSG_RUN_BACKUP:
// snapshot the pending-backup set and work on that
File oldJournal = mJournal;
RandomAccessFile oldJournalStream = mJournalStream;
synchronized (mQueueLock) {
if (mBackupQueue == null) {
mBackupQueue = new ArrayList<BackupRequest>();
@@ -207,10 +263,22 @@ class BackupManagerService extends IBackupManager.Stub {
mPendingBackups = new HashMap<ApplicationInfo,BackupRequest>();
}
// !!! TODO: start a new backup-queue journal file too
// WARNING: If we crash after this line, anything in mPendingBackups will
// be lost. FIX THIS.
if (mJournalStream != null) {
try {
mJournalStream.close();
} catch (IOException e) {
// don't need to do anything
}
makeJournalLocked();
}
// At this point, we have started a new journal file, and the old
// file identity is being passed to the backup processing thread.
// When it completes successfully, that old journal file will be
// deleted. If we crash prior to that, the old journal is parsed
// at next boot and the journaled requests fulfilled.
}
(new PerformBackupThread(mTransportId, mBackupQueue)).run();
(new PerformBackupThread(mTransportId, mBackupQueue, oldJournal)).run();
break;
case MSG_RUN_FULL_BACKUP:
@@ -433,10 +501,13 @@ class BackupManagerService extends IBackupManager.Stub {
private static final String TAG = "PerformBackupThread";
int mTransport;
ArrayList<BackupRequest> mQueue;
File mJournal;
public PerformBackupThread(int transportId, ArrayList<BackupRequest> queue) {
public PerformBackupThread(int transportId, ArrayList<BackupRequest> queue,
File journal) {
mTransport = transportId;
mQueue = queue;
mJournal = journal;
}
@Override
@@ -468,6 +539,10 @@ class BackupManagerService extends IBackupManager.Stub {
Log.e(TAG, "Error ending transport");
e.printStackTrace();
}
if (!mJournal.delete()) {
Log.e(TAG, "Unable to remove backup journal file " + mJournal.getAbsolutePath());
}
}
private void doQueuedBackups(IBackupTransport transport) {
@@ -782,7 +857,9 @@ class BackupManagerService extends IBackupManager.Stub {
// one already there, then overwrite it, but no harm done.
BackupRequest req = new BackupRequest(app, false);
mPendingBackups.put(app, req);
// !!! TODO: write to the pending-backup journal file in case of crash
// Journal this request in case of crash
writeToJournalLocked(packageName);
}
}
@@ -804,6 +881,18 @@ class BackupManagerService extends IBackupManager.Stub {
}
}
private void writeToJournalLocked(String str) {
if (mJournalStream != null) {
try {
mJournalStream.writeUTF(str);
} catch (IOException e) {
Log.e(TAG, "Error writing to backup journal");
mJournalStream = null;
mJournal = null;
}
}
}
// Schedule a backup pass for a given package. This method will schedule a
// full backup even for apps that do not declare an android:backupAgent, so
// use with care.