From a7368eadd9053ed846733da1e1ed10e18e0d242c Mon Sep 17 00:00:00 2001 From: Martin Fietz Date: Tue, 8 Mar 2016 15:11:28 +0100 Subject: [PATCH 1/4] Enable WAL and use non-exclusive transactions --- .../antennapod/core/storage/PodDBAdapter.java | 287 ++++++++++++------ 1 file changed, 188 insertions(+), 99 deletions(-) diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java index ffe5b2f24..2bde6da9d 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java @@ -10,6 +10,7 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; import android.media.MediaMetadataRetriever; +import android.os.Build; import android.text.TextUtils; import android.util.Log; @@ -323,6 +324,9 @@ public class PodDBAdapter { Log.v(TAG, "Opening DB"); try { db = dbHelper.getWritableDatabase(); + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + db.enableWriteAheadLogging(); + } } catch (SQLException ex) { Log.e(TAG, Log.getStackTraceString(ex)); db = dbHelper.getReadableDatabase(); @@ -425,34 +429,46 @@ public class PodDBAdapter { */ public long setImage(FeedImage image) { boolean startedTransaction = false; - if(!db.inTransaction()) { - db.beginTransaction(); - startedTransaction = true; - } - ContentValues values = new ContentValues(); - values.put(KEY_TITLE, image.getTitle()); - values.put(KEY_DOWNLOAD_URL, image.getDownload_url()); - values.put(KEY_DOWNLOADED, image.isDownloaded()); - values.put(KEY_FILE_URL, image.getFile_url()); - if (image.getId() == 0) { - image.setId(db.insert(TABLE_NAME_FEED_IMAGES, null, values)); - } else { - db.update(TABLE_NAME_FEED_IMAGES, values, KEY_ID + "=?", - new String[]{String.valueOf(image.getId())}); - } - - final FeedComponent owner = image.getOwner(); - if (owner != null && owner.getId() != 0) { - values.clear(); - values.put(KEY_IMAGE, image.getId()); - if (owner instanceof Feed) { - db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(image.getOwner().getId())}); + try { + if (!db.inTransaction()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + db.beginTransactionNonExclusive(); + } else { + db.beginTransaction(); + } + startedTransaction = true; + } + + ContentValues values = new ContentValues(); + values.put(KEY_TITLE, image.getTitle()); + values.put(KEY_DOWNLOAD_URL, image.getDownload_url()); + values.put(KEY_DOWNLOADED, image.isDownloaded()); + values.put(KEY_FILE_URL, image.getFile_url()); + if (image.getId() == 0) { + image.setId(db.insert(TABLE_NAME_FEED_IMAGES, null, values)); + } else { + db.update(TABLE_NAME_FEED_IMAGES, values, KEY_ID + "=?", + new String[]{String.valueOf(image.getId())}); + } + + final FeedComponent owner = image.getOwner(); + if (owner != null && owner.getId() != 0) { + values.clear(); + values.put(KEY_IMAGE, image.getId()); + if (owner instanceof Feed) { + db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(image.getOwner().getId())}); + } + } + if (startedTransaction) { + db.setTransactionSuccessful(); + } + } catch (SQLException e) { + Log.e(TAG, Log.getStackTraceString(e)); + } finally { + if (startedTransaction) { + db.endTransaction(); } - } - if(startedTransaction) { - db.setTransactionSuccessful(); - db.endTransaction(); } return image.getId(); } @@ -522,20 +538,29 @@ public class PodDBAdapter { * transaction */ public void setCompleteFeed(Feed... feeds) { - db.beginTransaction(); - for (Feed feed : feeds) { - setFeed(feed); - if (feed.getItems() != null) { - for (FeedItem item : feed.getItems()) { - setFeedItem(item, false); + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + db.beginTransactionNonExclusive(); + } else { + db.beginTransaction(); + } + for (Feed feed : feeds) { + setFeed(feed); + if (feed.getItems() != null) { + for (FeedItem item : feed.getItems()) { + setFeedItem(item, false); + } + } + if (feed.getPreferences() != null) { + setFeedPreferences(feed.getPreferences()); } } - if (feed.getPreferences() != null) { - setFeedPreferences(feed.getPreferences()); - } + db.setTransactionSuccessful(); + } catch (SQLException e) { + Log.e(TAG, Log.getStackTraceString(e)); + } finally { + db.endTransaction(); } - db.setTransactionSuccessful(); - db.endTransaction(); } /** @@ -598,19 +623,38 @@ public class PodDBAdapter { } public void setFeedItemlist(List items) { - db.beginTransaction(); - for (FeedItem item : items) { - setFeedItem(item, true); + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + db.beginTransactionNonExclusive(); + } else { + db.beginTransaction(); + } + for (FeedItem item : items) { + setFeedItem(item, true); + } + db.setTransactionSuccessful(); + } catch (SQLException e) { + Log.e(TAG, Log.getStackTraceString(e)); + } finally { + db.endTransaction(); } - db.setTransactionSuccessful(); - db.endTransaction(); } public long setSingleFeedItem(FeedItem item) { - db.beginTransaction(); - long result = setFeedItem(item, true); - db.setTransactionSuccessful(); - db.endTransaction(); + long result = 0; + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + db.beginTransactionNonExclusive(); + } else { + db.beginTransaction(); + } + result = setFeedItem(item, true); + db.setTransactionSuccessful(); + } catch (SQLException e) { + Log.e(TAG, Log.getStackTraceString(e)); + } finally { + db.endTransaction(); + } return result; } @@ -728,20 +772,29 @@ public class PodDBAdapter { public void setFeedItemRead(int played, long itemId, long mediaId, boolean resetMediaPosition) { - db.beginTransaction(); - ContentValues values = new ContentValues(); + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + db.beginTransactionNonExclusive(); + } else { + db.beginTransaction(); + } + ContentValues values = new ContentValues(); - values.put(KEY_READ, played); - db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(itemId)}); + values.put(KEY_READ, played); + db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(itemId)}); - if (resetMediaPosition) { - values.clear(); - values.put(KEY_POSITION, 0); - db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", new String[]{String.valueOf(mediaId)}); + if (resetMediaPosition) { + values.clear(); + values.put(KEY_POSITION, 0); + db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", new String[]{String.valueOf(mediaId)}); + } + + db.setTransactionSuccessful(); + } catch (SQLException e) { + Log.e(TAG, Log.getStackTraceString(e)); + } finally { + db.endTransaction(); } - - db.setTransactionSuccessful(); - db.endTransaction(); } /** @@ -750,15 +803,24 @@ public class PodDBAdapter { * @param itemIds items to change the value of */ public void setFeedItemRead(int read, long... itemIds) { - db.beginTransaction(); - ContentValues values = new ContentValues(); - for (long id : itemIds) { - values.clear(); - values.put(KEY_READ, read); - db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(id)}); + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + db.beginTransactionNonExclusive(); + } else { + db.beginTransaction(); + } + ContentValues values = new ContentValues(); + for (long id : itemIds) { + values.clear(); + values.put(KEY_READ, read); + db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(id)}); + } + db.setTransactionSuccessful(); + } catch (SQLException e) { + Log.e(TAG, Log.getStackTraceString(e)); + } finally { + db.endTransaction(); } - db.setTransactionSuccessful(); - db.endTransaction(); } public void setChapters(FeedItem item) { @@ -822,17 +884,26 @@ public class PodDBAdapter { public void setFavorites(List favorites) { ContentValues values = new ContentValues(); - db.beginTransaction(); - db.delete(TABLE_NAME_FAVORITES, null, null); - for (int i = 0; i < favorites.size(); i++) { - FeedItem item = favorites.get(i); - values.put(KEY_ID, i); - values.put(KEY_FEEDITEM, item.getId()); - values.put(KEY_FEED, item.getFeed().getId()); - db.insertWithOnConflict(TABLE_NAME_FAVORITES, null, values, SQLiteDatabase.CONFLICT_REPLACE); + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + db.beginTransactionNonExclusive(); + } else { + db.beginTransaction(); + } + db.delete(TABLE_NAME_FAVORITES, null, null); + for (int i = 0; i < favorites.size(); i++) { + FeedItem item = favorites.get(i); + values.put(KEY_ID, i); + values.put(KEY_FEEDITEM, item.getId()); + values.put(KEY_FEED, item.getFeed().getId()); + db.insertWithOnConflict(TABLE_NAME_FAVORITES, null, values, SQLiteDatabase.CONFLICT_REPLACE); + } + db.setTransactionSuccessful(); + } catch (SQLException e) { + Log.e(TAG, Log.getStackTraceString(e)); + } finally { + db.endTransaction(); } - db.setTransactionSuccessful(); - db.endTransaction(); } /** @@ -880,17 +951,26 @@ public class PodDBAdapter { public void setQueue(List queue) { ContentValues values = new ContentValues(); - db.beginTransaction(); - db.delete(TABLE_NAME_QUEUE, null, null); - for (int i = 0; i < queue.size(); i++) { - FeedItem item = queue.get(i); - values.put(KEY_ID, i); - values.put(KEY_FEEDITEM, item.getId()); - values.put(KEY_FEED, item.getFeed().getId()); - db.insertWithOnConflict(TABLE_NAME_QUEUE, null, values, SQLiteDatabase.CONFLICT_REPLACE); + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + db.beginTransactionNonExclusive(); + } else { + db.beginTransaction(); + } + db.delete(TABLE_NAME_QUEUE, null, null); + for (int i = 0; i < queue.size(); i++) { + FeedItem item = queue.get(i); + values.put(KEY_ID, i); + values.put(KEY_FEEDITEM, item.getId()); + values.put(KEY_FEED, item.getFeed().getId()); + db.insertWithOnConflict(TABLE_NAME_QUEUE, null, values, SQLiteDatabase.CONFLICT_REPLACE); + } + db.setTransactionSuccessful(); + } catch (SQLException e) { + Log.e(TAG, Log.getStackTraceString(e)); + } finally { + db.endTransaction(); } - db.setTransactionSuccessful(); - db.endTransaction(); } public void clearQueue() { @@ -937,23 +1017,32 @@ public class PodDBAdapter { * Remove a feed with all its FeedItems and Media entries. */ public void removeFeed(Feed feed) { - db.beginTransaction(); - if (feed.getImage() != null) { - removeFeedImage(feed.getImage()); - } - if (feed.getItems() != null) { - for (FeedItem item : feed.getItems()) { - removeFeedItem(item); + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + db.beginTransactionNonExclusive(); + } else { + db.beginTransaction(); } - } - // delete download log entries for feed - db.delete(TABLE_NAME_DOWNLOAD_LOG, KEY_FEEDFILE + "=? AND " + KEY_FEEDFILETYPE +"=?", - new String[] { String.valueOf(feed.getId()), String.valueOf(Feed.FEEDFILETYPE_FEED) }); + if (feed.getImage() != null) { + removeFeedImage(feed.getImage()); + } + if (feed.getItems() != null) { + for (FeedItem item : feed.getItems()) { + removeFeedItem(item); + } + } + // delete download log entries for feed + db.delete(TABLE_NAME_DOWNLOAD_LOG, KEY_FEEDFILE + "=? AND " + KEY_FEEDFILETYPE + "=?", + new String[]{String.valueOf(feed.getId()), String.valueOf(Feed.FEEDFILETYPE_FEED)}); - db.delete(TABLE_NAME_FEEDS, KEY_ID + "=?", + db.delete(TABLE_NAME_FEEDS, KEY_ID + "=?", new String[]{String.valueOf(feed.getId())}); - db.setTransactionSuccessful(); - db.endTransaction(); + db.setTransactionSuccessful(); + } catch (SQLException e) { + Log.e(TAG, Log.getStackTraceString(e)); + } finally { + db.endTransaction(); + } } public void clearPlaybackHistory() { From 391d059252a6da48ce44b80ed7c477a509e3a4ba Mon Sep 17 00:00:00 2001 From: Martin Fietz Date: Tue, 22 Mar 2016 18:49:45 +0100 Subject: [PATCH 2/4] Tests: Initialize DB adapter --- .../java/de/test/antennapod/storage/DBCleanupTests.java | 1 + .../de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java | 3 +-- .../java/de/test/antennapod/storage/DBReaderTest.java | 1 + .../java/de/test/antennapod/storage/DBTasksTest.java | 2 +- .../java/de/test/antennapod/storage/DBWriterTest.java | 1 + 5 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java b/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java index 7300df395..5e5eb1e8b 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java @@ -70,6 +70,7 @@ public class DBCleanupTests extends InstrumentationTestCase { assertTrue(destFolder.canWrite()); // create new database + PodDBAdapter.init(context); PodDBAdapter.deleteDatabase(); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java index 7205b42c4..74b34a176 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java @@ -19,8 +19,6 @@ import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.PodDBAdapter; -import static de.test.antennapod.storage.DBTestUtils.saveFeedlist; - /** * Tests that the APNullCleanupAlgorithm is working correctly. */ @@ -60,6 +58,7 @@ public class DBNullCleanupAlgorithmTest extends InstrumentationTestCase { assertTrue(destFolder.canWrite()); // create new database + PodDBAdapter.init(getInstrumentation().getTargetContext()); PodDBAdapter.deleteDatabase(); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java index 0fc3b1892..9386e3bd6 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java @@ -34,6 +34,7 @@ public class DBReaderTest extends InstrumentationTestCase { super.setUp(); // create new database + PodDBAdapter.init(getInstrumentation().getTargetContext()); PodDBAdapter.deleteDatabase(); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java index 5b2393d45..ceaf91080 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java @@ -28,7 +28,6 @@ public class DBTasksTest extends InstrumentationTestCase { @Override protected void tearDown() throws Exception { super.tearDown(); - assertTrue(PodDBAdapter.deleteDatabase()); } @@ -38,6 +37,7 @@ public class DBTasksTest extends InstrumentationTestCase { context = getInstrumentation().getTargetContext(); // create new database + PodDBAdapter.init(getInstrumentation().getTargetContext()); PodDBAdapter.deleteDatabase(); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java index 0e1d19f7b..b016902c2 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java @@ -53,6 +53,7 @@ public class DBWriterTest extends InstrumentationTestCase { super.setUp(); // create new database + PodDBAdapter.init(getInstrumentation().getTargetContext()); PodDBAdapter.deleteDatabase(); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); From 33567fe7d7f6c22806c9146947303d2b4d4c7acf Mon Sep 17 00:00:00 2001 From: Martin Fietz Date: Tue, 22 Mar 2016 18:49:56 +0100 Subject: [PATCH 3/4] Tests: Close adapter --- .../java/de/test/antennapod/storage/DBWriterTest.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java index b016902c2..40083e507 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java @@ -195,6 +195,7 @@ public class DBWriterTest extends InstrumentationTestCase { assertEquals(0, c.getCount()); c.close(); } + adapter.close(); } public void testDeleteFeedNoImage() throws ExecutionException, InterruptedException, IOException, TimeoutException { @@ -251,6 +252,7 @@ public class DBWriterTest extends InstrumentationTestCase { assertTrue(c.getCount() == 0); c.close(); } + adapter.close(); } public void testDeleteFeedNoItems() throws IOException, ExecutionException, InterruptedException, TimeoutException { @@ -288,6 +290,7 @@ public class DBWriterTest extends InstrumentationTestCase { c = adapter.getImageCursor(String.valueOf(image.getId())); assertTrue(c.getCount() == 0); c.close(); + adapter.close(); } public void testDeleteFeedNoFeedMedia() throws IOException, ExecutionException, InterruptedException, TimeoutException { @@ -340,6 +343,7 @@ public class DBWriterTest extends InstrumentationTestCase { assertTrue(c.getCount() == 0); c.close(); } + adapter.close(); } public void testDeleteFeedWithItemImages() throws InterruptedException, ExecutionException, TimeoutException, IOException { @@ -398,6 +402,7 @@ public class DBWriterTest extends InstrumentationTestCase { assertEquals(0, c.getCount()); c.close(); } + adapter.close(); } public void testDeleteFeedWithQueueItems() throws ExecutionException, InterruptedException, TimeoutException { @@ -528,6 +533,7 @@ public class DBWriterTest extends InstrumentationTestCase { assertTrue(c.getCount() == 0); c.close(); } + adapter.close(); } private FeedMedia playbackHistorySetup(Date playbackCompletionDate) { @@ -731,7 +737,6 @@ public class DBWriterTest extends InstrumentationTestCase { } assertTrue(idFound); } - queue.close(); adapter.close(); } From 62e2095a5ab83ee2361ce0051d8a9bf2c170f0e5 Mon Sep 17 00:00:00 2001 From: Martin Fietz Date: Tue, 22 Mar 2016 18:50:50 +0100 Subject: [PATCH 4/4] Close DB --- .../antennapod/core/storage/PodDBAdapter.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java index 2bde6da9d..c8df0f572 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java @@ -17,6 +17,7 @@ import android.util.Log; import java.util.Arrays; import java.util.List; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.event.ProgressEvent; @@ -304,7 +305,7 @@ public class PodDBAdapter { private static SQLiteDatabase db; private static Context context; private static PodDBHelper dbHelper; - private static int counter = 0; + private static AtomicInteger counter = new AtomicInteger(0); public static void init(Context context) { PodDBAdapter.context = context.getApplicationContext(); @@ -319,12 +320,13 @@ public class PodDBAdapter { private PodDBAdapter() {} - public PodDBAdapter open() { + public synchronized PodDBAdapter open() { + int adapters = counter.incrementAndGet(); + Log.v(TAG, "Opening DB #" + adapters); if (db == null || !db.isOpen() || db.isReadOnly()) { - Log.v(TAG, "Opening DB"); try { db = dbHelper.getWritableDatabase(); - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { db.enableWriteAheadLogging(); } } catch (SQLException ex) { @@ -335,8 +337,13 @@ public class PodDBAdapter { return this; } - public void close() { - // do nothing + public synchronized void close() { + int adapters = counter.decrementAndGet(); + Log.v(TAG, "Closing DB #" + adapters); + if(adapters == 0) { + Log.v(TAG, "Closing DB, really"); + db.close(); + } } public static boolean deleteDatabase() {