blob: 9e10370e3fae0ac38aa9aea89ced8bd01bc80c8f [file] [log] [blame]
Christopher Tatede6f1312009-07-07 13:11:41 -07001/*
2 * Copyright (C) 2009 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
17package com.android.browser;
18
19import java.io.IOException;
20
21import android.app.BackupAgent;
22import android.backup.BackupDataInput;
23import android.backup.BackupDataOutput;
24import android.database.Cursor;
25import android.os.ParcelFileDescriptor;
26import android.provider.Browser;
27import android.provider.Browser.BookmarkColumns;
28
29import java.io.DataInputStream;
30import java.io.DataOutputStream;
31import java.io.EOFException;
32import java.io.File;
33import java.io.FileInputStream;
34import java.io.FileOutputStream;
35
36import java.util.zip.CRC32;
37
38/**
39 * Settings backup agent for the Android browser. Currently the only thing
40 * stored is the set of bookmarks. It's okay if I/O exceptions are thrown
41 * out of the agent; the calling code handles it and the backup operation
42 * simply fails.
43 */
44public class BrowserBackupAgent extends BackupAgent {
45 static final String BOOKMARK_KEY = "_bookmarks_";
46
47 /**
48 * In order to determine whether the bookmark set has changed since the
49 * last time we did a backup, we store the following bits of info in the
50 * state file after a backup:
51 *
52 * 1. the size of the flattened bookmark file
53 * 2. the CRC32 of that file
54 *
55 * After we flatten the bookmarks file here in onBackup, we compare its
56 * metrics with the values from the saved state. If they match, it means
57 * the bookmarks didn't really change and we don't need to send the data.
58 * (If they don't match, of course, then they've changed and we do indeed
59 * send the new flattened file to be backed up.)
60 */
61 @Override
62 public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
63 ParcelFileDescriptor newState) throws IOException {
64 long savedFileSize = -1;
65 long savedCrc = -1;
66
67 // Extract the previous bookmark file size & CRC from the saved state
68 DataInputStream in = new DataInputStream(
69 new FileInputStream(oldState.getFileDescriptor()));
70 try {
71 savedFileSize = in.readLong();
72 savedCrc = in.readLong();
73 } catch (EOFException e) {
74 // It means we had no previous state; that's fine
75 }
76
77 // TODO: BUILD THE FLATTENED BOOKMARK FILE FROM THE DB (into tmpfile)
78 File tmpfile = getFilesDir().createTempFile("bkp", null);
79 CRC32 crc = new CRC32();
80 try {
81 Cursor cursor = getContentResolver().query(Browser.BOOKMARKS_URI,
82 new String[] { BookmarkColumns.URL, BookmarkColumns.VISITS,
83 BookmarkColumns.DATE, BookmarkColumns.CREATED,
84 BookmarkColumns.TITLE },
85 BookmarkColumns.BOOKMARK + " == 1 ", null, null);
86 int count = cursor.getCount();
87 FileOutputStream out = new FileOutputStream(tmpfile);
88 for (int i = 0; i < count; i++) {
89 StringBuilder sb = new StringBuilder();
90 // URL
91 sb.append("'");
92 sb.append(cursor.getString(0));
93 sb.append("','");
94 // VISITS
95 sb.append(cursor.getInt(1));
96 sb.append("','");
97 // DATE
98 sb.append(cursor.getLong(2));
99 sb.append("','");
100 // CREATED
101 sb.append(cursor.getLong(3));
102 sb.append("','");
103 // TITLE
104 sb.append(cursor.getString(4));
105 sb.append("'");
106 out.write(sb.toString().getBytes());
Christopher Tateb4645a12009-07-10 13:36:58 -0700107
108 cursor.moveToNext();
Christopher Tatede6f1312009-07-07 13:11:41 -0700109 }
110 out.close();
111 /*
112 android.util.Log.d("s", "backing up data" +
113 getContentResolver().openFileDescriptor(Browser.BOOKMARKS_URI, "r").toString());
114 */
115 // NOTE: feed the flattened data through the crc engine on the fly
116 // to save re-reading it later just to checksum it
117
118 // Once the file is built, compare its metrics with the saved ones
119 if ((crc.getValue() != savedCrc) || (tmpfile.length() != savedFileSize)) {
120 // Different checksum or different size, so we need to back it up
121 copyFileToBackup(BOOKMARK_KEY, tmpfile, data);
122 }
123
124 // Last, record the metrics of the bookmark file that we just stored
125 writeBackupState(tmpfile.length(), crc.getValue(), newState);
126 } finally {
127 // Make sure to tidy up when we're done
128 tmpfile.delete();
129 }
130 }
131
132 /**
133 * Restore from backup -- reads in the flattened bookmark file as supplied from
134 * the backup service, parses that out, and rebuilds the bookmarks table in the
135 * browser database from it.
136 */
137 @Override
138 public void onRestore(BackupDataInput data, int appVersionCode,
139 ParcelFileDescriptor newState) throws IOException {
140 long crc = -1;
141 File tmpfile = getFilesDir().createTempFile("rst", null);
142 try {
143 while (data.readNextHeader()) {
144 if (BOOKMARK_KEY.equals(data.getKey())) {
145 // Read the flattened bookmark data into a temp file
146 crc = copyBackupToFile(data, tmpfile, data.getDataSize());
147
148 // TODO: READ THE FLAT BOOKMARKS FILE 'tmpfile' AND REBUILD THE DB TABLE
149 }
150
151 // Last, write the state we just restored from so we can discern
152 // changes whenever we get invoked for backup in the future
153 writeBackupState(tmpfile.length(), crc, newState);
154 }
155 } finally {
156 // Whatever happens, delete the temp file
157 tmpfile.delete();
158 }
159 }
160
161 /*
162 * Utility functions
163 */
164
165 // Write the file to backup as a single record under the given key
166 private void copyFileToBackup(String key, File file, BackupDataOutput data)
167 throws IOException {
168 final int CHUNK = 8192;
169 byte[] buf = new byte[CHUNK];
170
171 int toCopy = (int) file.length();
172 data.writeEntityHeader(key, toCopy);
173
174 FileInputStream in = new FileInputStream(file);
175 int nRead;
176 while (toCopy > 0) {
177 nRead = in.read(buf, 0, CHUNK);
178 data.writeEntityData(buf, nRead);
179 toCopy -= nRead;
180 }
181 in.close();
182 }
183
184 // Read the given file from backup to a file, calculating a CRC32 along the way
185 private long copyBackupToFile(BackupDataInput data, File file, int toRead)
186 throws IOException {
187 final int CHUNK = 8192;
188 byte[] buf = new byte[CHUNK];
189 CRC32 crc = new CRC32();
190
191 while (toRead > 0) {
192 int numRead = data.readEntityData(buf, 0, CHUNK);
193 crc.update(buf, 0, numRead);
194 toRead -= numRead;
195 }
196
197 return crc.getValue();
198 }
199
200 // Write the given metrics to the new state file
201 private void writeBackupState(long fileSize, long crc, ParcelFileDescriptor stateFile)
202 throws IOException {
203 DataOutputStream out = new DataOutputStream(
204 new FileOutputStream(stateFile.getFileDescriptor()));
205 out.writeLong(fileSize);
206 out.writeLong(crc);
207 }
208}