diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/FotoGalleryActivity.java b/app/src/main/java/de/k3b/android/androFotoFinder/FotoGalleryActivity.java index e6d12be3..daaefb9c 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/FotoGalleryActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/FotoGalleryActivity.java @@ -213,6 +213,11 @@ public boolean onOptionsItemSelected(MenuItem item) { if (0 != onDbUpdateCommand(item)) notifyPhotoChanged(); return true; + + case R.id.cmd_db_backup: + onDbBackup(); + return true; + case R.id.cmd_more: new Handler().postDelayed(new Runnable() { public void run() { @@ -227,6 +232,10 @@ public void run() { } + private void onDbBackup() { + AndroFotoFinderApp.getMediaContent2DbUpdateService().createBackup(); + } + private int onDbUpdateCommand(MenuItem item) { Activity activity = this; int count = AndroFotoFinderApp.getMediaContent2DbUpdateService().update(this, null); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/DatabaseHelper.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/DatabaseHelper.java index 14653642..307cbf80 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/DatabaseHelper.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/DatabaseHelper.java @@ -20,15 +20,19 @@ package de.k3b.android.androFotoFinder.queries; import android.content.Context; +import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; import java.io.File; +import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.transactionlog.TransactionLogSql; import de.k3b.android.util.DatabaseContext; +import static de.k3b.android.androFotoFinder.queries.FotoSql.LOG_TAG; + /** * Created by k3b on 22.02.2017. */ @@ -68,8 +72,53 @@ private static DatabaseHelper getInstance(Context context) { } - public static void version2Upgrade_RecreateMediDbCopy(final SQLiteDatabase db) { - for (String sql : MediaDBRepository.Impl.DDL) { + public static void version2Upgrade_ReCreateMediaDbTable(final SQLiteDatabase db) { + execSql(db, "(Re)CreateMediaDbTable:", MediaDBRepository.Impl.DDL); + } + + public static void createBackup(SQLiteDatabase db) { + if (tableExists(db, MediaDBRepository.Impl.DATABASE_TABLE_NAME)) { + // see https://www.techonthenet.com/sqlite/tables/create_table_as.php + + if (!tableExists(db, MediaDBRepository.Impl.DATABASE_TABLE_NAME_BACKUP)) { + execSql(db, "create Backup:", MediaDBRepository.Impl.CREATE_BACKUP); + } else { + execSql(db, "update Backup:", MediaDBRepository.Impl.UPDATE_BACKUP); + } + } + } + + public static void restoreFromBackup(SQLiteDatabase db) { + if (tableExists(db, MediaDBRepository.Impl.DATABASE_TABLE_NAME_BACKUP)) { + // see https://stackoverflow.com/questions/19270259/update-with-join-in-sqlite + execSql(db, "restoreFromBackup:", MediaDBRepository.Impl.RESTORE_FROM_BACKUP); + } + } + + // from https://stackoverflow.com/questions/1601151/how-do-i-check-in-sqlite-whether-a-table-exists + private static boolean tableExists(SQLiteDatabase db, String tableName) { + if (tableName == null || db == null || !db.isOpen()) { + return false; + } + Cursor cursor = db.rawQuery( + "SELECT COUNT(*) FROM sqlite_master WHERE type = ? AND name = ?", + new String[]{"table", tableName} + ); + if (!cursor.moveToFirst()) { + cursor.close(); + return false; + } + int count = cursor.getInt(0); + cursor.close(); + return count > 0; + } + + private static void execSql(SQLiteDatabase db, String dbgContext, String... ddlStatements) { + + for (String sql : ddlStatements) { + if (Global.debugEnabledSql) { + Log.i(LOG_TAG, "DatabaseHelper-" + dbgContext + sql); + } db.execSQL(sql); } } @@ -79,9 +128,9 @@ public static void version2Upgrade_RecreateMediDbCopy(final SQLiteDatabase db) { */ @Override public void onCreate(final SQLiteDatabase db) { - db.execSQL(TransactionLogSql.CREATE_TABLE); + execSql(db, "First Create DB: ", TransactionLogSql.CREATE_TABLE); - this.version2Upgrade_RecreateMediDbCopy(db); + version2Upgrade_ReCreateMediaDbTable(db); } @Override @@ -90,7 +139,7 @@ public void onUpgrade(final SQLiteDatabase db, final int oldVersion, Log.w(this.getClass().toString(), "Upgrading database from version " + oldVersion + " to " + newVersion + ". (Old data is kept.)"); if (oldVersion < DatabaseHelper.DATABASE_VERSION_2_MEDIA_DB_COPY) { - this.version2Upgrade_RecreateMediDbCopy(db); + version2Upgrade_ReCreateMediaDbTable(db); } } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaContent2DBUpdateService.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaContent2DBUpdateService.java index ee157c1e..4bc01475 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaContent2DBUpdateService.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaContent2DBUpdateService.java @@ -55,13 +55,22 @@ public MediaContent2DBUpdateService(Context context, SQLiteDatabase writableData } public void clearMediaCopy() { - DatabaseHelper.version2Upgrade_RecreateMediDbCopy(writableDatabase); + DatabaseHelper.version2Upgrade_ReCreateMediaDbTable(writableDatabase); } public int rebuild(Context context, IProgessListener progessListener) { long start = new Date().getTime(); + if (progessListener != null) + progessListener.onProgress(0, 0, "Create Backup of tags, lat, lon, rating"); + createBackup(); + if (progessListener != null) progessListener.onProgress(0, 0, "Recreate Database"); clearMediaCopy(); + if (progessListener != null) + progessListener.onProgress(0, 0, "Copy from Android Media Database"); int changeCount = MediaDBRepository.Impl.updateMediaCopy(context, writableDatabase, null, null, progessListener); + if (progessListener != null) + progessListener.onProgress(0, 0, "Restore tags, lat, lon, rating from Backup"); + DatabaseHelper.restoreFromBackup(writableDatabase); long timeInSecs = (new Date().getTime() - start) / 1000; final String text = "load db " + timeInSecs + " secs"; Toast.makeText(context, text, Toast.LENGTH_LONG).show(); @@ -69,6 +78,10 @@ public int rebuild(Context context, IProgessListener progessListener) { return changeCount; } + public void createBackup() { + DatabaseHelper.createBackup(writableDatabase); + } + public int update(Context context, IProgessListener progessListener) { long start = new Date().getTime(); int changeCount = MediaDBRepository.Impl.updateMediaCopy(context, writableDatabase, progessListener); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBRepository.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBRepository.java index 3845d958..3a2e185e 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBRepository.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBRepository.java @@ -412,13 +412,14 @@ public static ContentValues getContentValues(String fullFilePathFilter, ContentV public static class Impl { public static final String DATABASE_TABLE_NAME = "files"; + public static final String DATABASE_TABLE_NAME_BACKUP = "backup"; /** * SQL to create copy of contentprovider MediaStore.Images. * copied from android-4.4 android database. Removed columns not used */ protected static final String[] DDL = new String[]{ - "DROP TABLE IF EXISTS \"files\"", - "CREATE TABLE \"files\" (\n" + + "DROP TABLE IF EXISTS \"" + DATABASE_TABLE_NAME + "\"", + "CREATE TABLE \"" + DATABASE_TABLE_NAME + "\" (\n" + "\t_id INTEGER PRIMARY KEY AUTOINCREMENT,\n" + "\t_size INTEGER,\n" + "\tdate_added INTEGER,\n" + @@ -441,11 +442,37 @@ public static class Impl { "\tlatitude DOUBLE,\n" + "\tlongitude DOUBLE\n" + "\t )", - "CREATE INDEX media_type_index ON files(media_type)", - "CREATE INDEX path_index ON files(_data)", - "CREATE INDEX sort_index ON files(datetaken ASC, _id ASC)", - "CREATE INDEX title_idx ON files(title)", + "CREATE INDEX media_type_index ON " + DATABASE_TABLE_NAME + "(media_type)", + "CREATE INDEX path_index ON " + DATABASE_TABLE_NAME + "(_data)", + "CREATE INDEX sort_index ON " + DATABASE_TABLE_NAME + "(datetaken ASC, _id ASC)", + "CREATE INDEX title_idx ON " + DATABASE_TABLE_NAME + "(title)", }; + protected static final String[] RESTORE_FROM_BACKUP = new String[]{ + "UPDATE " + DATABASE_TABLE_NAME + "\n" + + "SET latitude = (SELECT latitude FROM " + DATABASE_TABLE_NAME_BACKUP + " WHERE _data = " + DATABASE_TABLE_NAME + "._data)\n" + + ", latitude = (SELECT latitude FROM " + DATABASE_TABLE_NAME_BACKUP + " WHERE _data = " + DATABASE_TABLE_NAME + "._data)\n" + + ", longitude = (SELECT longitude FROM " + DATABASE_TABLE_NAME_BACKUP + " WHERE _data = " + DATABASE_TABLE_NAME + "._data)\n" + + ", tags = (SELECT tags FROM " + DATABASE_TABLE_NAME_BACKUP + " WHERE _data = " + DATABASE_TABLE_NAME + "._data)\n" + + ", duration = (SELECT duration FROM " + DATABASE_TABLE_NAME_BACKUP + " WHERE _data = " + DATABASE_TABLE_NAME + "._data)\n" + + ", title = (SELECT title FROM " + DATABASE_TABLE_NAME_BACKUP + " WHERE _data = " + DATABASE_TABLE_NAME + "._data)\n" + + ", description = (SELECT description FROM " + DATABASE_TABLE_NAME_BACKUP + " WHERE _data = " + DATABASE_TABLE_NAME + "._data)\n" + + ", bookmark = (SELECT bookmark FROM " + DATABASE_TABLE_NAME_BACKUP + " WHERE _data = " + DATABASE_TABLE_NAME + "._data)", + }; + private static final String HAS_DATA = "latitude is not null or tags is not null or bookmark is not null"; + private static final String MY_COLUMNS = " _id, _data, latitude, longitude, tags, duration, bookmark, title, description "; + private static final String SELECT_FROM_FILES = " SELECT" + MY_COLUMNS + " from " + DATABASE_TABLE_NAME + "" + " where " + HAS_DATA; + protected static final String[] CREATE_BACKUP = new String[]{ + "DROP TABLE IF EXISTS \"" + DATABASE_TABLE_NAME_BACKUP + "\"", + "CREATE TABLE \"" + DATABASE_TABLE_NAME_BACKUP + "\" AS" + SELECT_FROM_FILES, + "CREATE INDEX bu_path_index ON " + DATABASE_TABLE_NAME_BACKUP + "(_data)", + }; + protected static final String[] UPDATE_BACKUP = new String[]{ + "DELETE FROM " + DATABASE_TABLE_NAME_BACKUP + " where exists " + + "(select _data from " + DATABASE_TABLE_NAME + + " WHERE _data = " + DATABASE_TABLE_NAME_BACKUP + "._data" + " AND (" + HAS_DATA + "))", + "INSERT INTO " + DATABASE_TABLE_NAME_BACKUP + "(" + MY_COLUMNS + ")" + SELECT_FROM_FILES, + }; + private static final int COL_INT_MIN = 0; // same colum order as in DDL private static final String[] USED_MEDIA_COLUMNS = new String[]{ diff --git a/app/src/main/res/menu/menu_gallery_ao10.xml b/app/src/main/res/menu/menu_gallery_ao10.xml index 55dc9efe..7c5c0e76 100644 --- a/app/src/main/res/menu/menu_gallery_ao10.xml +++ b/app/src/main/res/menu/menu_gallery_ao10.xml @@ -50,6 +50,13 @@ android:title="@string/update_db_menu_title" android:visible="true" /> + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index ade8fcbf..4aafd681 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -253,6 +253,7 @@ der SD-Karte oder des USB-Sticks aus.

Mediendatenbank (neu) laden Mediendatenbank aktualisieren + Backup lat,long,tags,... in Mediendatenbank Media-Scanner diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 555036ab..42d3fa90 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -295,6 +295,7 @@ of SD-Card or USB-Stick.

Mediadatabase ... (Re)Load Mediadatabase Update Mediadatabase + Backup lat,long,tags,... in Mediadatabase Mediafile scanner diff --git a/fastlane/metadata/android/en-US/changelogs/49.txt b/fastlane/metadata/android/en-US/changelogs/49.txt index ad8d1f98..ef6fc762 100644 --- a/fastlane/metadata/android/en-US/changelogs/49.txt +++ b/fastlane/metadata/android/en-US/changelogs/49.txt @@ -3,3 +3,4 @@ Changes from 0.8.4 to ??? * #155: Android-10 support (experimental) * #173: incremental media scanner * #173: Ao10-performance-Optimized-Mediacanner +* #190: Backup/Restore lat/long/tags/rating \ No newline at end of file