From 4155c693deca2b941b30d674232938c729707ecf Mon Sep 17 00:00:00 2001 From: Fabian Bender Date: Tue, 17 Sep 2024 13:30:32 +0200 Subject: [PATCH 1/5] catch every `SQLiteException` to restore from it --- core/src/main/java/io/snabble/sdk/ProductDatabase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/io/snabble/sdk/ProductDatabase.java b/core/src/main/java/io/snabble/sdk/ProductDatabase.java index 60c346df3d..407ebac0fc 100644 --- a/core/src/main/java/io/snabble/sdk/ProductDatabase.java +++ b/core/src/main/java/io/snabble/sdk/ProductDatabase.java @@ -338,7 +338,7 @@ synchronized void applyDeltaUpdate(InputStream inputStream) throws IOException { try { tempDb = SQLiteDatabase.openOrCreateDatabase(tempDbFile, null); - } catch (SQLiteCantOpenDatabaseException e) { + } catch (SQLiteException e) { project.logErrorEvent("Could not open or create db: %s", e.getMessage()); throw new IOException("Could not open or create db", e); } From fd2a4178c6e3d6a4c6733f9bb3bcc9edb7246f4e Mon Sep 17 00:00:00 2001 From: Fabian Bender Date: Tue, 17 Sep 2024 13:39:14 +0200 Subject: [PATCH 2/5] double check to make sure it works --- .../java/io/snabble/sdk/ProductDatabase.java | 147 +++++++++--------- 1 file changed, 74 insertions(+), 73 deletions(-) diff --git a/core/src/main/java/io/snabble/sdk/ProductDatabase.java b/core/src/main/java/io/snabble/sdk/ProductDatabase.java index 407ebac0fc..cd72db9303 100644 --- a/core/src/main/java/io/snabble/sdk/ProductDatabase.java +++ b/core/src/main/java/io/snabble/sdk/ProductDatabase.java @@ -6,7 +6,6 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.database.Cursor; -import android.database.sqlite.SQLiteCantOpenDatabaseException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.os.CancellationSignal; @@ -336,92 +335,94 @@ synchronized void applyDeltaUpdate(InputStream inputStream) throws IOException { } final SQLiteDatabase tempDb; - try { - tempDb = SQLiteDatabase.openOrCreateDatabase(tempDbFile, null); - } catch (SQLiteException e) { - project.logErrorEvent("Could not open or create db: %s", e.getMessage()); - throw new IOException("Could not open or create db", e); - } + if (tempDbFile.canRead() && tempDbFile.canWrite()) { + try { + tempDb = SQLiteDatabase.openOrCreateDatabase(tempDbFile, null); + } catch (SQLiteException e) { + project.logErrorEvent("Could not open or create db: %s", e.getMessage()); + throw new IOException("Could not open or create db", e); + } - Scanner scanner = new Scanner(inputStream, "UTF-8"); + Scanner scanner = new Scanner(inputStream, "UTF-8"); - //delta update statements are split by ;\n\n - occurrences of ;\n\n in strings are - //escaped replaced by the backend - splitting by just ; is not enough because of eventual - //occurrences in database rows - scanner.useDelimiter(";\n\n"); + //delta update statements are split by ;\n\n - occurrences of ;\n\n in strings are + //escaped replaced by the backend - splitting by just ; is not enough because of eventual + //occurrences in database rows + scanner.useDelimiter(";\n\n"); - try { - tempDb.beginTransaction(); - } catch (SQLiteException e) { - project.logErrorEvent("Could not apply delta update: Could not access temp database"); - tempDb.close(); - throw new IOException(); - } - - while (scanner.hasNext()) { try { - String line = scanner.next(); - - //Scanner catches IOExceptions and stores the last thrown exception in ioException() - //because we want to handle possible IOExceptions we throw them here again if they - //occurred - IOException lastException = scanner.ioException(); - if (lastException != null) { - project.logErrorEvent("Could not apply delta update: %s", lastException.getMessage()); - tempDb.close(); - deleteDatabase(tempDbFile); - throw lastException; + tempDb.beginTransaction(); + } catch (SQLiteException e) { + project.logErrorEvent("Could not apply delta update: Could not access temp database"); + tempDb.close(); + throw new IOException(); + } + + while (scanner.hasNext()) { + try { + String line = scanner.next(); + + //Scanner catches IOExceptions and stores the last thrown exception in ioException() + //because we want to handle possible IOExceptions we throw them here again if they + //occurred + IOException lastException = scanner.ioException(); + if (lastException != null) { + project.logErrorEvent("Could not apply delta update: %s", lastException.getMessage()); + tempDb.close(); + deleteDatabase(tempDbFile); + throw lastException; + } + + tempDb.execSQL(line); + } catch (SQLiteException e) { + //code 0 = "not an error" - statements like empty strings are causing that exception + //which we want to allow + if (!e.getMessage().contains("code 0")) { + project.logErrorEvent("Could not apply delta update: %s", e.getMessage()); + tempDb.close(); + deleteDatabase(tempDbFile); + throw new IOException(); + } } + } - tempDb.execSQL(line); + try { + tempDb.setTransactionSuccessful(); + tempDb.endTransaction(); } catch (SQLiteException e) { - //code 0 = "not an error" - statements like empty strings are causing that exception - //which we want to allow - if (!e.getMessage().contains("code 0")) { - project.logErrorEvent("Could not apply delta update: %s", e.getMessage()); - tempDb.close(); - deleteDatabase(tempDbFile); - throw new IOException(); - } + project.logErrorEvent("Could not apply delta update: Could not finish transaction on temp database"); + tempDb.close(); + throw new IOException(); } - } - try { - tempDb.setTransactionSuccessful(); - tempDb.endTransaction(); - } catch (SQLiteException e) { - project.logErrorEvent("Could not apply delta update: Could not finish transaction on temp database"); - tempDb.close(); - throw new IOException(); - } + //delta updates are making the database grow larger and larger, so we vacuum here + //to keep the database as small as possible + long vacuumTime = SystemClock.elapsedRealtime(); + try { + tempDb.execSQL("VACUUM"); + } catch (SQLiteException e) { + project.logErrorEvent("Could not apply delta update: %s", e.getMessage()); + tempDb.close(); + deleteDatabase(tempDbFile); + throw new IOException(); + } - //delta updates are making the database grow larger and larger, so we vacuum here - //to keep the database as small as possible - long vacuumTime = SystemClock.elapsedRealtime(); - try { - tempDb.execSQL("VACUUM"); - } catch (SQLiteException e) { - project.logErrorEvent("Could not apply delta update: %s", e.getMessage()); - tempDb.close(); - deleteDatabase(tempDbFile); - throw new IOException(); - } + long vacuumTime2 = SystemClock.elapsedRealtime() - vacuumTime; + Logger.d("VACUUM took %d ms", vacuumTime2); - long vacuumTime2 = SystemClock.elapsedRealtime() - vacuumTime; - Logger.d("VACUUM took %d ms", vacuumTime2); + tempDb.close(); - tempDb.close(); + try { + swap(tempDbFile); + } catch (IOException e) { + project.logErrorEvent("Could not apply delta update: %s", e.getMessage()); + tempDb.close(); + throw new IOException(); + } - try { - swap(tempDbFile); - } catch (IOException e) { - project.logErrorEvent("Could not apply delta update: %s", e.getMessage()); - tempDb.close(); - throw new IOException(); + long time2 = SystemClock.elapsedRealtime() - time; + Logger.d("Delta update (%d -> %d) took %d ms", fromRevisionId, getRevisionId(), time2); } - - long time2 = SystemClock.elapsedRealtime() - time; - Logger.d("Delta update (%d -> %d) took %d ms", fromRevisionId, getRevisionId(), time2); } /** From 4c7e068f75255aea87aa377f787aa703f84299f5 Mon Sep 17 00:00:00 2001 From: Christian Maier Date: Tue, 17 Sep 2024 14:20:02 +0200 Subject: [PATCH 3/5] Early exit instead of indentation --- .../java/io/snabble/sdk/ProductDatabase.java | 146 +++++++++--------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/core/src/main/java/io/snabble/sdk/ProductDatabase.java b/core/src/main/java/io/snabble/sdk/ProductDatabase.java index cd72db9303..9044b13f7f 100644 --- a/core/src/main/java/io/snabble/sdk/ProductDatabase.java +++ b/core/src/main/java/io/snabble/sdk/ProductDatabase.java @@ -335,94 +335,94 @@ synchronized void applyDeltaUpdate(InputStream inputStream) throws IOException { } final SQLiteDatabase tempDb; - if (tempDbFile.canRead() && tempDbFile.canWrite()) { - try { - tempDb = SQLiteDatabase.openOrCreateDatabase(tempDbFile, null); - } catch (SQLiteException e) { - project.logErrorEvent("Could not open or create db: %s", e.getMessage()); - throw new IOException("Could not open or create db", e); - } + if (!tempDbFile.canRead() || !tempDbFile.canWrite()) return; - Scanner scanner = new Scanner(inputStream, "UTF-8"); + try { + tempDb = SQLiteDatabase.openOrCreateDatabase(tempDbFile, null); + } catch (SQLiteException e) { + project.logErrorEvent("Could not open or create db: %s", e.getMessage()); + throw new IOException("Could not open or create db", e); + } - //delta update statements are split by ;\n\n - occurrences of ;\n\n in strings are - //escaped replaced by the backend - splitting by just ; is not enough because of eventual - //occurrences in database rows - scanner.useDelimiter(";\n\n"); + Scanner scanner = new Scanner(inputStream, "UTF-8"); - try { - tempDb.beginTransaction(); - } catch (SQLiteException e) { - project.logErrorEvent("Could not apply delta update: Could not access temp database"); - tempDb.close(); - throw new IOException(); - } + //delta update statements are split by ;\n\n - occurrences of ;\n\n in strings are + //escaped replaced by the backend - splitting by just ; is not enough because of eventual + //occurrences in database rows + scanner.useDelimiter(";\n\n"); - while (scanner.hasNext()) { - try { - String line = scanner.next(); - - //Scanner catches IOExceptions and stores the last thrown exception in ioException() - //because we want to handle possible IOExceptions we throw them here again if they - //occurred - IOException lastException = scanner.ioException(); - if (lastException != null) { - project.logErrorEvent("Could not apply delta update: %s", lastException.getMessage()); - tempDb.close(); - deleteDatabase(tempDbFile); - throw lastException; - } + try { + tempDb.beginTransaction(); + } catch (SQLiteException e) { + project.logErrorEvent("Could not apply delta update: Could not access temp database"); + tempDb.close(); + throw new IOException(); + } - tempDb.execSQL(line); - } catch (SQLiteException e) { - //code 0 = "not an error" - statements like empty strings are causing that exception - //which we want to allow - if (!e.getMessage().contains("code 0")) { - project.logErrorEvent("Could not apply delta update: %s", e.getMessage()); - tempDb.close(); - deleteDatabase(tempDbFile); - throw new IOException(); - } + while (scanner.hasNext()) { + try { + String line = scanner.next(); + + //Scanner catches IOExceptions and stores the last thrown exception in ioException() + //because we want to handle possible IOExceptions we throw them here again if they + //occurred + IOException lastException = scanner.ioException(); + if (lastException != null) { + project.logErrorEvent("Could not apply delta update: %s", lastException.getMessage()); + tempDb.close(); + deleteDatabase(tempDbFile); + throw lastException; } - } - try { - tempDb.setTransactionSuccessful(); - tempDb.endTransaction(); + tempDb.execSQL(line); } catch (SQLiteException e) { - project.logErrorEvent("Could not apply delta update: Could not finish transaction on temp database"); - tempDb.close(); - throw new IOException(); - } - - //delta updates are making the database grow larger and larger, so we vacuum here - //to keep the database as small as possible - long vacuumTime = SystemClock.elapsedRealtime(); - try { - tempDb.execSQL("VACUUM"); - } catch (SQLiteException e) { - project.logErrorEvent("Could not apply delta update: %s", e.getMessage()); - tempDb.close(); - deleteDatabase(tempDbFile); - throw new IOException(); + //code 0 = "not an error" - statements like empty strings are causing that exception + //which we want to allow + if (!e.getMessage().contains("code 0")) { + project.logErrorEvent("Could not apply delta update: %s", e.getMessage()); + tempDb.close(); + deleteDatabase(tempDbFile); + throw new IOException(); + } } + } - long vacuumTime2 = SystemClock.elapsedRealtime() - vacuumTime; - Logger.d("VACUUM took %d ms", vacuumTime2); + try { + tempDb.setTransactionSuccessful(); + tempDb.endTransaction(); + } catch (SQLiteException e) { + project.logErrorEvent("Could not apply delta update: Could not finish transaction on temp database"); + tempDb.close(); + throw new IOException(); + } + //delta updates are making the database grow larger and larger, so we vacuum here + //to keep the database as small as possible + long vacuumTime = SystemClock.elapsedRealtime(); + try { + tempDb.execSQL("VACUUM"); + } catch (SQLiteException e) { + project.logErrorEvent("Could not apply delta update: %s", e.getMessage()); tempDb.close(); + deleteDatabase(tempDbFile); + throw new IOException(); + } - try { - swap(tempDbFile); - } catch (IOException e) { - project.logErrorEvent("Could not apply delta update: %s", e.getMessage()); - tempDb.close(); - throw new IOException(); - } + long vacuumTime2 = SystemClock.elapsedRealtime() - vacuumTime; + Logger.d("VACUUM took %d ms", vacuumTime2); + + tempDb.close(); - long time2 = SystemClock.elapsedRealtime() - time; - Logger.d("Delta update (%d -> %d) took %d ms", fromRevisionId, getRevisionId(), time2); + try { + swap(tempDbFile); + } catch (IOException e) { + project.logErrorEvent("Could not apply delta update: %s", e.getMessage()); + tempDb.close(); + throw new IOException(); } + + long time2 = SystemClock.elapsedRealtime() - time; + Logger.d("Delta update (%d -> %d) took %d ms", fromRevisionId, getRevisionId(), time2); } /** From e072006e831bec9a2c73269ce188e990cf19d0ee Mon Sep 17 00:00:00 2001 From: Christian Maier Date: Tue, 17 Sep 2024 14:23:08 +0200 Subject: [PATCH 4/5] Remove early exit and throw IOException instead --- core/src/main/java/io/snabble/sdk/ProductDatabase.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/io/snabble/sdk/ProductDatabase.java b/core/src/main/java/io/snabble/sdk/ProductDatabase.java index 9044b13f7f..c6721741bc 100644 --- a/core/src/main/java/io/snabble/sdk/ProductDatabase.java +++ b/core/src/main/java/io/snabble/sdk/ProductDatabase.java @@ -335,7 +335,8 @@ synchronized void applyDeltaUpdate(InputStream inputStream) throws IOException { } final SQLiteDatabase tempDb; - if (!tempDbFile.canRead() || !tempDbFile.canWrite()) return; + if (!tempDbFile.canRead() || !tempDbFile.canWrite()) + throw new IOException("TempDbFile cannot be read and/or written."); try { tempDb = SQLiteDatabase.openOrCreateDatabase(tempDbFile, null); From 5aceec29602531ff2bc1f839bb581a039fe23bc5 Mon Sep 17 00:00:00 2001 From: Fabian Bender Date: Tue, 17 Sep 2024 14:29:16 +0200 Subject: [PATCH 5/5] add missing changes --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a095fe3151..1fba349e19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file. ### Fixed * ui: subject dialog for external billing now works in dark mode * ui: external billing icon works with dark mode now +* core: prevent SQLiteLockedException and recover from it on product database update ## [0.75.7] ### Added