mobile/android/base/db/SharedBrowserDatabaseProvider.java

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:0b18d1972484
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 package org.mozilla.gecko.db;
6
7 import org.mozilla.gecko.db.BrowserContract.CommonColumns;
8 import org.mozilla.gecko.db.BrowserContract.SyncColumns;
9 import org.mozilla.gecko.db.PerProfileDatabases.DatabaseHelperFactory;
10
11 import android.content.Context;
12 import android.database.Cursor;
13 import android.database.sqlite.SQLiteDatabase;
14 import android.net.Uri;
15 import android.util.Log;
16
17 /**
18 * A ContentProvider subclass that provides per-profile browser.db access
19 * that can be safely shared between multiple providers.
20 *
21 * If multiple ContentProvider classes wish to share a database, it's
22 * vitally important that they use the same SQLiteOpenHelpers for access.
23 *
24 * Failure to do so can cause accidental concurrent writes, with the result
25 * being unexpected SQLITE_BUSY errors.
26 *
27 * This class provides a static {@link PerProfileDatabases} instance, lazily
28 * initialized within {@link SharedBrowserDatabaseProvider#onCreate()}.
29 */
30 public abstract class SharedBrowserDatabaseProvider extends AbstractPerProfileDatabaseProvider {
31 private static final String LOGTAG = SharedBrowserDatabaseProvider.class.getSimpleName();
32
33 private static PerProfileDatabases<BrowserDatabaseHelper> databases;
34
35 @Override
36 protected PerProfileDatabases<BrowserDatabaseHelper> getDatabases() {
37 return databases;
38 }
39
40 @Override
41 public boolean onCreate() {
42 // If necessary, do the shared DB work.
43 synchronized (SharedBrowserDatabaseProvider.class) {
44 if (databases != null) {
45 return true;
46 }
47
48 final DatabaseHelperFactory<BrowserDatabaseHelper> helperFactory = new DatabaseHelperFactory<BrowserDatabaseHelper>() {
49 @Override
50 public BrowserDatabaseHelper makeDatabaseHelper(Context context, String databasePath) {
51 return new BrowserDatabaseHelper(context, databasePath);
52 }
53 };
54
55 databases = new PerProfileDatabases<BrowserDatabaseHelper>(getContext(), BrowserDatabaseHelper.DATABASE_NAME, helperFactory);
56 }
57
58 return true;
59 }
60
61 /**
62 * Clean up some deleted records from the specified table.
63 *
64 * If called in an existing transaction, it is the caller's responsibility
65 * to ensure that the transaction is already upgraded to a writer, because
66 * this method issues a read followed by a write, and thus is potentially
67 * vulnerable to an unhandled SQLITE_BUSY failure during the upgrade.
68 *
69 * If not called in an existing transaction, no new explicit transaction
70 * will be begun.
71 */
72 protected void cleanUpSomeDeletedRecords(Uri fromUri, String tableName) {
73 Log.d(LOGTAG, "Cleaning up deleted records from " + tableName);
74
75 // We clean up records marked as deleted that are older than a
76 // predefined max age. It's important not be too greedy here and
77 // remove only a few old deleted records at a time.
78
79 // we cleanup records marked as deleted that are older than a
80 // predefined max age. It's important not be too greedy here and
81 // remove only a few old deleted records at a time.
82
83 // Maximum age of deleted records to be cleaned up (20 days in ms)
84 final long MAX_AGE_OF_DELETED_RECORDS = 86400000 * 20;
85
86 // Number of records marked as deleted to be removed
87 final long DELETED_RECORDS_PURGE_LIMIT = 5;
88
89 // Android SQLite doesn't have LIMIT on DELETE. Instead, query for the
90 // IDs of matching rows, then delete them in one go.
91 final long now = System.currentTimeMillis();
92 final String selection = SyncColumns.IS_DELETED + " = 1 AND " +
93 SyncColumns.DATE_MODIFIED + " <= " +
94 (now - MAX_AGE_OF_DELETED_RECORDS);
95
96 final String profile = fromUri.getQueryParameter(BrowserContract.PARAM_PROFILE);
97 final SQLiteDatabase db = getWritableDatabaseForProfile(profile, isTest(fromUri));
98 final String[] ids;
99 final String limit = Long.toString(DELETED_RECORDS_PURGE_LIMIT, 10);
100 final Cursor cursor = db.query(tableName, new String[] { CommonColumns._ID }, selection, null, null, null, null, limit);
101 try {
102 ids = new String[cursor.getCount()];
103 int i = 0;
104 while (cursor.moveToNext()) {
105 ids[i++] = Long.toString(cursor.getLong(0), 10);
106 }
107 } finally {
108 cursor.close();
109 }
110
111 final String inClause = computeSQLInClause(ids.length,
112 CommonColumns._ID);
113 db.delete(tableName, inClause, ids);
114 }
115 }

mercurial