diff options
author | Christopher Tate <ctate@android.com> | 2009-07-10 17:51:48 -0700 |
---|---|---|
committer | Christopher Tate <ctate@android.com> | 2009-07-27 16:35:27 -0700 |
commit | 9c0dd8caacff99ba76bbb9dc2cab156cded505a8 (patch) | |
tree | d3a4d257bf67f6529261df9a71e351ece9b89a6e /src/com/android/browser | |
parent | 09377c370181683273264d625784c7bc2415dedd (diff) | |
download | packages_apps_Browser-9c0dd8caacff99ba76bbb9dc2cab156cded505a8.zip packages_apps_Browser-9c0dd8caacff99ba76bbb9dc2cab156cded505a8.tar.gz packages_apps_Browser-9c0dd8caacff99ba76bbb9dc2cab156cded505a8.tar.bz2 |
First real cut of bookmarks backup agent
The agent now does backup/restore of the bookmarks table. Whether to back up is
determined by flattening the bookmark table, CRCing the flattened
representation, and comparing the CRC with the previous backup pass's version.
If they differ (or if the file size differs), the table is deemed to have
changed and we send the flattened file off to the server as a single key.
On restore, the bookmark records are read individually from the flattened
representation, the existing bookmarks table is queried for a matching URL, and
if none exists the bookmark is inserted into the table.
Bookmarks.addBookmark() now takes a boolean argument "retainIcon." When false,
the implementation will not call into the web icon database. This is necessary
during restore because the web icon database is only available from the Browser
app's main thread, but restore happens without the app proper being involved.
The other call points for addBookmark() have been updated to pass 'true,' i.e.
preserving the current behavior.
Diffstat (limited to 'src/com/android/browser')
-rw-r--r-- | src/com/android/browser/AddBookmarkPage.java | 2 | ||||
-rw-r--r-- | src/com/android/browser/Bookmarks.java | 10 | ||||
-rw-r--r-- | src/com/android/browser/BrowserBackupAgent.java | 181 | ||||
-rw-r--r-- | src/com/android/browser/BrowserProvider.java | 29 | ||||
-rw-r--r-- | src/com/android/browser/HistoryItem.java | 2 |
5 files changed, 171 insertions, 53 deletions
diff --git a/src/com/android/browser/AddBookmarkPage.java b/src/com/android/browser/AddBookmarkPage.java index e827a7e..191659a 100644 --- a/src/com/android/browser/AddBookmarkPage.java +++ b/src/com/android/browser/AddBookmarkPage.java @@ -142,7 +142,7 @@ public class AddBookmarkPage extends Activity { setResult(RESULT_OK, (new Intent()).setAction( getIntent().toString()).putExtras(mMap)); } else { - Bookmarks.addBookmark(null, getContentResolver(), url, title); + Bookmarks.addBookmark(null, getContentResolver(), url, title, true); setResult(RESULT_OK); } } catch (IllegalStateException e) { diff --git a/src/com/android/browser/Bookmarks.java b/src/com/android/browser/Bookmarks.java index 97e6b20..a3dc919 100644 --- a/src/com/android/browser/Bookmarks.java +++ b/src/com/android/browser/Bookmarks.java @@ -47,9 +47,13 @@ import java.util.Date; * @param cr The ContentResolver being used to add the bookmark to the db. * @param url URL of the website to be bookmarked. * @param name Provided name for the bookmark. + * @param retainIcon Whether to retain the page's icon in the icon database. + * This will usually be <code>true</code> except when bookmarks are + * added by a settings restore agent. */ /* package */ static void addBookmark(Context context, - ContentResolver cr, String url, String name) { + ContentResolver cr, String url, String name, + boolean retainIcon) { // Want to append to the beginning of the list long creationTime = new Date().getTime(); // First we check to see if the user has already visited this @@ -137,7 +141,9 @@ import java.util.Date; cr.insert(Browser.BOOKMARKS_URI, map); } } - WebIconDatabase.getInstance().retainIconForPageUrl(url); + if (retainIcon) { + WebIconDatabase.getInstance().retainIconForPageUrl(url); + } cursor.deactivate(); if (context != null) { Toast.makeText(context, R.string.added_to_bookmarks, diff --git a/src/com/android/browser/BrowserBackupAgent.java b/src/com/android/browser/BrowserBackupAgent.java index 9e10370..c239b12 100644 --- a/src/com/android/browser/BrowserBackupAgent.java +++ b/src/com/android/browser/BrowserBackupAgent.java @@ -25,14 +25,16 @@ import android.database.Cursor; import android.os.ParcelFileDescriptor; import android.provider.Browser; import android.provider.Browser.BookmarkColumns; +import android.util.Log; +import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; - +import java.util.ArrayList; import java.util.zip.CRC32; /** @@ -40,9 +42,16 @@ import java.util.zip.CRC32; * stored is the set of bookmarks. It's okay if I/O exceptions are thrown * out of the agent; the calling code handles it and the backup operation * simply fails. + * + * @hide */ public class BrowserBackupAgent extends BackupAgent { + static final String TAG = "BrowserBookmarkAgent"; + static final boolean DEBUG = true; + static final String BOOKMARK_KEY = "_bookmarks_"; + /** this version num MUST be incremented if the flattened-file schema ever changes */ + static final int BACKUP_AGENT_VERSION = 0; /** * In order to determine whether the bookmark set has changed since the @@ -51,6 +60,7 @@ public class BrowserBackupAgent extends BackupAgent { * * 1. the size of the flattened bookmark file * 2. the CRC32 of that file + * 3. the agent version number [relevant following an OTA] * * After we flatten the bookmarks file here in onBackup, we compare its * metrics with the values from the saved state. If they match, it means @@ -63,6 +73,7 @@ public class BrowserBackupAgent extends BackupAgent { ParcelFileDescriptor newState) throws IOException { long savedFileSize = -1; long savedCrc = -1; + int savedVersion = -1; // Extract the previous bookmark file size & CRC from the saved state DataInputStream in = new DataInputStream( @@ -70,59 +81,28 @@ public class BrowserBackupAgent extends BackupAgent { try { savedFileSize = in.readLong(); savedCrc = in.readLong(); + savedVersion = in.readInt(); } catch (EOFException e) { // It means we had no previous state; that's fine } - // TODO: BUILD THE FLATTENED BOOKMARK FILE FROM THE DB (into tmpfile) - File tmpfile = getFilesDir().createTempFile("bkp", null); - CRC32 crc = new CRC32(); + // Build a flattened representation of the bookmarks table + File tmpfile = File.createTempFile("bkp", null, getCacheDir()); try { - Cursor cursor = getContentResolver().query(Browser.BOOKMARKS_URI, - new String[] { BookmarkColumns.URL, BookmarkColumns.VISITS, - BookmarkColumns.DATE, BookmarkColumns.CREATED, - BookmarkColumns.TITLE }, - BookmarkColumns.BOOKMARK + " == 1 ", null, null); - int count = cursor.getCount(); - FileOutputStream out = new FileOutputStream(tmpfile); - for (int i = 0; i < count; i++) { - StringBuilder sb = new StringBuilder(); - // URL - sb.append("'"); - sb.append(cursor.getString(0)); - sb.append("','"); - // VISITS - sb.append(cursor.getInt(1)); - sb.append("','"); - // DATE - sb.append(cursor.getLong(2)); - sb.append("','"); - // CREATED - sb.append(cursor.getLong(3)); - sb.append("','"); - // TITLE - sb.append(cursor.getString(4)); - sb.append("'"); - out.write(sb.toString().getBytes()); - - cursor.moveToNext(); - } - out.close(); - /* - android.util.Log.d("s", "backing up data" + - getContentResolver().openFileDescriptor(Browser.BOOKMARKS_URI, "r").toString()); - */ - // NOTE: feed the flattened data through the crc engine on the fly - // to save re-reading it later just to checksum it - - // Once the file is built, compare its metrics with the saved ones - if ((crc.getValue() != savedCrc) || (tmpfile.length() != savedFileSize)) { + FileOutputStream outfstream = new FileOutputStream(tmpfile); + long newCrc = buildBookmarkFile(outfstream); + outfstream.close(); + + // Any changes since the last backup? + if ((savedVersion != BACKUP_AGENT_VERSION) + || (newCrc != savedCrc) + || (tmpfile.length() != savedFileSize)) { // Different checksum or different size, so we need to back it up copyFileToBackup(BOOKMARK_KEY, tmpfile, data); } - // Last, record the metrics of the bookmark file that we just stored - writeBackupState(tmpfile.length(), crc.getValue(), newState); + // Record our backup state and we're done + writeBackupState(tmpfile.length(), newCrc, newState); } finally { // Make sure to tidy up when we're done tmpfile.delete(); @@ -138,14 +118,60 @@ public class BrowserBackupAgent extends BackupAgent { public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException { long crc = -1; - File tmpfile = getFilesDir().createTempFile("rst", null); + File tmpfile = File.createTempFile("rst", null, getFilesDir()); try { while (data.readNextHeader()) { if (BOOKMARK_KEY.equals(data.getKey())) { // Read the flattened bookmark data into a temp file crc = copyBackupToFile(data, tmpfile, data.getDataSize()); - // TODO: READ THE FLAT BOOKMARKS FILE 'tmpfile' AND REBUILD THE DB TABLE + FileInputStream infstream = new FileInputStream(tmpfile); + DataInputStream in = new DataInputStream(infstream); + + try { + int count = in.readInt(); + ArrayList<Bookmark> bookmarks = new ArrayList<Bookmark>(count); + + // Read all the bookmarks, then process later -- if we can't read + // all the data successfully, we don't touch the bookmarks table + for (int i = 0; i < count; i++) { + Bookmark mark = new Bookmark(); + mark.url = in.readUTF(); + mark.visits = in.readInt(); + mark.date = in.readLong(); + mark.created = in.readLong(); + mark.title = in.readUTF(); + bookmarks.add(mark); + } + + // Okay, we have all the bookmarks -- now see if we need to add + // them to the browser's database + int N = bookmarks.size(); + if (DEBUG) Log.v(TAG, "Restoring " + N + " bookmarks"); + String[] urlCol = new String[] { BookmarkColumns.URL }; + for (int i = 0; i < N; i++) { + Bookmark mark = bookmarks.get(i); + + // Does this URL exist in the bookmark table? + Cursor cursor = getContentResolver().query(Browser.BOOKMARKS_URI, + urlCol, BookmarkColumns.URL + " == '" + mark.url + "' AND " + + BookmarkColumns.BOOKMARK + " == 1 ", null, null); + // if not, insert it + if (cursor.getCount() <= 0) { + Log.v(TAG, "Did not see url: " + mark.url); + // Right now we do not reconstruct the db entry in its + // entirety; we just add a new bookmark with the same data + Bookmarks.addBookmark(null, getContentResolver(), + mark.url, mark.title, false); + } else { + Log.v(TAG, "Skipping extant url: " + mark.url); + } + cursor.close(); + } + } catch (IOException ioe) { + Log.w(TAG, "Bad backup data; not restoring"); + crc = -1; + } } // Last, write the state we just restored from so we can discern @@ -158,10 +184,67 @@ public class BrowserBackupAgent extends BackupAgent { } } + class Bookmark { + public String url; + public int visits; + public long date; + public long created; + public String title; + } /* * Utility functions */ + // Flatten the bookmarks table into the given file, calculating its CRC in the process + private long buildBookmarkFile(FileOutputStream outfstream) throws IOException { + CRC32 crc = new CRC32(); + ByteArrayOutputStream bufstream = new ByteArrayOutputStream(512); + DataOutputStream bout = new DataOutputStream(bufstream); + + Cursor cursor = getContentResolver().query(Browser.BOOKMARKS_URI, + new String[] { BookmarkColumns.URL, BookmarkColumns.VISITS, + BookmarkColumns.DATE, BookmarkColumns.CREATED, + BookmarkColumns.TITLE }, + BookmarkColumns.BOOKMARK + " == 1 ", null, null); + + // The first thing in the file is the row count... + int count = cursor.getCount(); + if (DEBUG) Log.v(TAG, "Backing up " + count + " bookmarks"); + bout.writeInt(count); + byte[] record = bufstream.toByteArray(); + crc.update(record); + outfstream.write(record); + + // ... followed by the data for each row + for (int i = 0; i < count; i++) { + cursor.moveToNext(); + + String url = cursor.getString(0); + int visits = cursor.getInt(1); + long date = cursor.getLong(2); + long created = cursor.getLong(3); + String title = cursor.getString(4); + + // construct the flattened record in a byte array + bufstream.reset(); + bout.writeUTF(url); + bout.writeInt(visits); + bout.writeLong(date); + bout.writeLong(created); + bout.writeUTF(title); + + // Update the CRC and write the record to the temp file + record = bufstream.toByteArray(); + crc.update(record); + outfstream.write(record); + + if (DEBUG) Log.v(TAG, " wrote url " + url); + } + + cursor.close(); + return crc.getValue(); + } + // Write the file to backup as a single record under the given key private void copyFileToBackup(String key, File file, BackupDataOutput data) throws IOException { @@ -187,13 +270,16 @@ public class BrowserBackupAgent extends BackupAgent { final int CHUNK = 8192; byte[] buf = new byte[CHUNK]; CRC32 crc = new CRC32(); + FileOutputStream out = new FileOutputStream(file); while (toRead > 0) { int numRead = data.readEntityData(buf, 0, CHUNK); crc.update(buf, 0, numRead); + out.write(buf, 0, numRead); toRead -= numRead; } + out.close(); return crc.getValue(); } @@ -204,5 +290,6 @@ public class BrowserBackupAgent extends BackupAgent { new FileOutputStream(stateFile.getFileDescriptor())); out.writeLong(fileSize); out.writeLong(crc); + out.writeInt(BACKUP_AGENT_VERSION); } } diff --git a/src/com/android/browser/BrowserProvider.java b/src/com/android/browser/BrowserProvider.java index 29c65e8..b74c9eb 100644 --- a/src/com/android/browser/BrowserProvider.java +++ b/src/com/android/browser/BrowserProvider.java @@ -19,6 +19,7 @@ package com.android.browser; import com.google.android.providers.GoogleSettings.Partner; import android.app.SearchManager; +import android.backup.BackupManager; import android.content.ComponentName; import android.content.ContentProvider; import android.content.ContentUris; @@ -54,6 +55,7 @@ import java.util.regex.Pattern; public class BrowserProvider extends ContentProvider { private SQLiteOpenHelper mOpenHelper; + private BackupManager mBackupManager; private static final String sDatabaseName = "browser.db"; private static final String TAG = "BrowserProvider"; private static final String ORDER_BY = "visits DESC, date DESC"; @@ -264,6 +266,7 @@ public class BrowserProvider extends ContentProvider { public boolean onCreate() { final Context context = getContext(); mOpenHelper = new DatabaseHelper(context); + mBackupManager = new BackupManager(context); // we added "picasa web album" into default bookmarks for version 19. // To avoid erasing the bookmark table, we added it explicitly for // version 18 and 19 as in the other cases, we will erase the table. @@ -736,6 +739,7 @@ public class BrowserProvider extends ContentProvider { @Override public Uri insert(Uri url, ContentValues initialValues) { + boolean isBookmarkTable = false; SQLiteDatabase db = mOpenHelper.getWritableDatabase(); int match = URI_MATCHER.match(url); @@ -749,6 +753,7 @@ public class BrowserProvider extends ContentProvider { uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI, rowID); } + isBookmarkTable = true; break; } @@ -771,6 +776,11 @@ public class BrowserProvider extends ContentProvider { throw new IllegalArgumentException("Unknown URL"); } getContext().getContentResolver().notifyChange(uri, null); + + // back up the new bookmark set if we just inserted one + if (isBookmarkTable) { + mBackupManager.dataChanged(); + } return uri; } @@ -783,7 +793,10 @@ public class BrowserProvider extends ContentProvider { throw new IllegalArgumentException("Unknown URL"); } - if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_SEARCHES_ID) { + // need to know whether it's the bookmarks table for a couple fo reasons + boolean isBookmarkTable = (match == URI_MATCH_BOOKMARKS_ID); + + if (isBookmarkTable || match == URI_MATCH_SEARCHES_ID) { StringBuilder sb = new StringBuilder(); if (where != null && where.length() > 0) { sb.append("( "); @@ -797,6 +810,11 @@ public class BrowserProvider extends ContentProvider { int count = db.delete(TABLE_NAMES[match % 10], where, whereArgs); getContext().getContentResolver().notifyChange(url, null); + + // back up the new bookmark set if we just deleted one + if (isBookmarkTable) { + mBackupManager.dataChanged(); + } return count; } @@ -810,7 +828,9 @@ public class BrowserProvider extends ContentProvider { throw new IllegalArgumentException("Unknown URL"); } - if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_SEARCHES_ID) { + boolean isBookmarkTable = (match == URI_MATCH_BOOKMARKS_ID); + + if (isBookmarkTable || match == URI_MATCH_SEARCHES_ID) { StringBuilder sb = new StringBuilder(); if (where != null && where.length() > 0) { sb.append("( "); @@ -824,6 +844,11 @@ public class BrowserProvider extends ContentProvider { int ret = db.update(TABLE_NAMES[match % 10], values, where, whereArgs); getContext().getContentResolver().notifyChange(url, null); + + // back up the new bookmark set if we just changed one + if (isBookmarkTable) { + mBackupManager.dataChanged(); + } return ret; } diff --git a/src/com/android/browser/HistoryItem.java b/src/com/android/browser/HistoryItem.java index b37a3bd..8a994f3 100644 --- a/src/com/android/browser/HistoryItem.java +++ b/src/com/android/browser/HistoryItem.java @@ -46,7 +46,7 @@ import android.widget.TextView; boolean isChecked) { if (isChecked) { Bookmarks.addBookmark(mContext, - mContext.getContentResolver(), mUrl, getName()); + mContext.getContentResolver(), mUrl, getName(), true); } else { Bookmarks.removeFromBookmarks(mContext, mContext.getContentResolver(), mUrl); |