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 0b8b95e2..c3a5a45d 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/FotoGalleryActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/FotoGalleryActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020 by k3b. + * Copyright (c) 2015-2021 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -208,6 +208,9 @@ public boolean onOptionsItemSelected(MenuItem item) { case R.id.cmd_db_reload: AndroFotoFinderApp.getMediaContent2DbUpdateService().rebuild(this, null); return true; + case R.id.cmd_db_update: + AndroFotoFinderApp.getMediaContent2DbUpdateService().update(this, null); + return true; case R.id.cmd_more: new Handler().postDelayed(new Runnable() { public void run() { diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/FotoSql.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/FotoSql.java index bce4ace5..9e72a8f8 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/FotoSql.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/FotoSql.java @@ -178,6 +178,11 @@ public class FotoSql extends FotoSqlBase { // SQL_COL_LAST_MODIFIED in seconds since 1970; "?" in milli-seconds since 1970 private static final String FILTER_EXPR_DATE_MODIFIED_MAX = SQL_COL_LAST_MODIFIED + " < ?"; private static final String FILTER_EXPR_DATE_MODIFIED_MIN = SQL_COL_LAST_MODIFIED + " >= ?"; + + // SQL_COL_DATE_ADDED in seconds since 1970; "?" in milli-seconds since 1970 + private static final String FILTER_EXPR_DATE_ADDED_MAX = SQL_COL_DATE_ADDED + " < ?"; + private static final String FILTER_EXPR_DATE_ADDED_MIN = SQL_COL_DATE_ADDED + " >= ?"; + protected static final String FILTER_EXPR_PATH_LIKE = "(" + SQL_COL_PATH + " like ?)"; // same format as dir. i.e. description='/2014/12/24/' or '/mnt/sdcard/pictures/' @@ -405,12 +410,26 @@ public static void addWhereDateMinMax(QueryParameter resultQuery, final long dat public static void addWhereDateModifiedMinMax(QueryParameter resultQuery, final long dateMinInMilliSecs1970, final long dateMaxInMilliSecs1970) { // SQL_COL_LAST_MODIFIED in seconds since 1970; translate from MilliSecs - if (dateMinInMilliSecs1970 != 0) resultQuery.addWhere(FILTER_EXPR_DATE_MODIFIED_MIN, Long.toString(dateMinInMilliSecs1970 / LAST_MODIFIED_FACTOR)); + if (dateMinInMilliSecs1970 != 0) + resultQuery.addWhere(FILTER_EXPR_DATE_MODIFIED_MIN, Long.toString(dateMinInMilliSecs1970 / LAST_MODIFIED_FACTOR)); + + if (dateMaxInMilliSecs1970 != 0) + resultQuery.addWhere(FILTER_EXPR_DATE_MODIFIED_MAX, Long.toString(dateMaxInMilliSecs1970 / LAST_MODIFIED_FACTOR)); + } - if (dateMaxInMilliSecs1970 != 0) resultQuery.addWhere(FILTER_EXPR_DATE_MODIFIED_MAX, Long.toString(dateMaxInMilliSecs1970 / LAST_MODIFIED_FACTOR)); + public static void addWhereDateAddedMinMax(QueryParameter resultQuery, final long dateMinInMilliSecs1970, final long dateMaxInMilliSecs1970) { + + // SQL_COL_DATE_ADDED in seconds since 1970; translate from MilliSecs + if (dateMinInMilliSecs1970 != 0) + resultQuery.addWhere(FILTER_EXPR_DATE_ADDED_MIN, Long.toString(dateMinInMilliSecs1970 / LAST_MODIFIED_FACTOR)); + + if (dateMaxInMilliSecs1970 != 0) + resultQuery.addWhere(FILTER_EXPR_DATE_ADDED_MAX, Long.toString(dateMaxInMilliSecs1970 / LAST_MODIFIED_FACTOR)); } - /** translates a query back to filter */ + /** + * translates a query back to filter + */ public static IGalleryFilter parseQuery(QueryParameter query, boolean removeFromSourceQuery) { if (query != null) { GalleryFilterParameter filter = new GalleryFilterParameter(); 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 a663a295..b40c5094 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 by k3b. + * Copyright (c) 2019-2021 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -29,7 +29,7 @@ import de.k3b.io.IProgessListener; /** - * #155: takes care that chages from + * #155: takes care that changes from * {@link MediaContentproviderRepository} are transfered to {@link MediaDBRepository} */ public class MediaContent2DBUpdateService { @@ -60,11 +60,21 @@ public void clearMediaCopy() { public void rebuild(Context context, IProgessListener progessListener) { long start = new Date().getTime(); clearMediaCopy(); - MediaDBRepository.Impl.updateMediaCopy(context, writableDatabase, null, progessListener); + MediaDBRepository.Impl.updateMediaCopy(context, writableDatabase, null, null, progessListener); start = (new Date().getTime() - start) / 1000; final String text = "load db " + start + " secs"; Toast.makeText(context, text, Toast.LENGTH_LONG).show(); if (progessListener != null) progessListener.onProgress(0, 0, text); + + } + + public void update(Context context, IProgessListener progessListener) { + long start = new Date().getTime(); + MediaDBRepository.Impl.updateMediaCopy(context, writableDatabase, progessListener); + start = (new Date().getTime() - start) / 1000; + final String text = "update db " + start + " secs"; + Toast.makeText(context, text, Toast.LENGTH_LONG).show(); + if (progessListener != null) progessListener.onProgress(0, 0, text); } 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 3c97778b..44faa632 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 @@ -20,6 +20,7 @@ import android.content.ContentValues; import android.content.Context; +import android.content.SharedPreferences; import android.database.Cursor; import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; @@ -27,10 +28,12 @@ import android.net.Uri; import android.os.Build; import android.os.CancellationSignal; +import android.preference.PreferenceManager; import android.provider.MediaStore; import android.util.Log; import java.sql.Date; +import java.util.Calendar; import de.k3b.LibGlobal; import de.k3b.android.androFotoFinder.Global; @@ -86,7 +89,9 @@ public MediaDBRepository(SQLiteDatabase db) { public Cursor createCursorForQuery(StringBuilder out_debugMessage, String dbgContext, QueryParameter parameters, VISIBILITY visibility, CancellationSignal cancellationSignal) { - if (visibility != null) FotoSql.setWhereVisibility(parameters, visibility); + if (visibility != null) { + FotoSql.setWhereVisibility(parameters, visibility); + } return createCursorForQuery(out_debugMessage, dbgContext, parameters.toWhere(), parameters.toAndroidParameters(), parameters.toGroupBy(), parameters.toHaving(), @@ -475,6 +480,7 @@ public static class Impl { .addColumn(USED_MEDIA_COLUMNS) .addFrom(SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME) .addWhere(FILTER_EXPR_AFFECTED_FILES); + private static long nextMonthTimeInSecs; private static boolean isLomg(int index) { return index >= intMin && index <= intMax; @@ -517,7 +523,7 @@ private static String getSqlUpdateWithParams() { return sql.toString(); } - private static int bindAndExecUpdate(Cursor c, SQLiteStatement sql) { + private static int bindAndExecUpdate(Cursor c, SQLiteStatement sql, long dateAdded, long dateUpdated) { sql.clearBindings(); // sql where @@ -538,10 +544,16 @@ private static int bindAndExecUpdate(Cursor c, SQLiteStatement sql) { sql.bindDouble(i, c.getDouble(i)); } } + if (dateAdded != 0) { + sql.bindLong(colDATE_ADDED, dateAdded); + } + if (dateUpdated != 0) { + sql.bindLong(colLAST_MODIFIED, dateUpdated); + } return sql.executeUpdateDelete(); } - private static void bindAndExecInsert(Cursor c, SQLiteStatement sql) { + private static void bindAndExecInsert(Cursor c, SQLiteStatement sql, long dateAdded, long dateUpdated) { sql.clearBindings(); for (int i = intMin; i <= intMax; i++) { @@ -559,6 +571,12 @@ private static void bindAndExecInsert(Cursor c, SQLiteStatement sql) { sql.bindDouble(i + 1, c.getDouble(i)); } } + if (dateAdded != 0) { + sql.bindLong(colDATE_ADDED + 1, dateAdded); + } + if (dateUpdated != 0) { + sql.bindLong(colLAST_MODIFIED + 1, dateUpdated); + } sql.executeInsert(); } @@ -590,19 +608,37 @@ public static void clearMedaiCopy(SQLiteDatabase db) { } } + public static int updateMediaCopy( + Context context, SQLiteDatabase db, + IProgessListener progessListener) { + SharedPreferences prefsInstance = PreferenceManager + .getDefaultSharedPreferences(context.getApplicationContext()); + long maxDateAddedSecs = prefsInstance.getLong("maxDateAddedSecs", 0l); + return updateMediaCopy(context, db, null, new Date(maxDateAddedSecs), progessListener); + } - public static int updateMediaCopy(Context context, SQLiteDatabase db, Date lastUpdate, IProgessListener progessListener) { + public static int updateMediaCopy( + Context context, SQLiteDatabase db, + Date filterLastUpdateMin, Date filterLastAddedMin, IProgessListener progessListener) { int progress = 0; java.util.Date startTime = new java.util.Date(); - QueryParameter query = queryGetAllColumns; - long _lastUpdate = (lastUpdate != null) ? (lastUpdate.getTime() / 1000L) : 0L; + QueryParameter query = new QueryParameter().getFrom(queryGetAllColumns); - if (_lastUpdate != 0) { - query = new QueryParameter().getFrom(queryGetAllColumns); - FotoSql.addWhereDateModifiedMinMax(query, _lastUpdate, 0); - // FotoSql.createCursorForQuery() + Calendar nextMonth = Calendar.getInstance(); + nextMonth.add(Calendar.MONTH, 1); + nextMonthTimeInSecs = nextMonth.getTimeInMillis() / 1000; + + long filterLastUpdateMinInMillis = (filterLastUpdateMin != null) ? (filterLastUpdateMin.getTime()) : 0L; + if (filterLastUpdateMinInMillis != 0) { + FotoSql.addWhereDateModifiedMinMax(query, filterLastUpdateMinInMillis, 0); + } + + long filterLastAddedMinInMillis = (filterLastAddedMin != null) ? (filterLastAddedMin.getTime()) : 0L; + if (filterLastAddedMinInMillis != 0) { + FotoSql.addWhereDateAddedMinMax(query, filterLastAddedMinInMillis, nextMonth.getTimeInMillis()); } + Cursor c = null; SQLiteStatement sqlInsert = null; SQLiteStatement sqlUpdate = null; @@ -611,7 +647,7 @@ public static int updateMediaCopy(Context context, SQLiteDatabase db, Date lastU int itemCount = 0; int insertCout = 0; int updateCount = 0; - // ContentValues contentValues = new ContentValues(); + try { db.beginTransaction(); // Performance boost: all db-inserts/updates in one transaction @@ -624,26 +660,36 @@ public static int updateMediaCopy(Context context, SQLiteDatabase db, Date lastU sqlInsert = db.compileStatement(getSqlInsertWithParams()); sqlUpdate = db.compileStatement(getSqlUpdateWithParams()); + long maxDateAddedSecs = 0; + long maxDateUpdatedSecs = 0; while (c.moveToNext()) { - // getContentValues(c, contentValues); - isUpdate = (c.getLong(colDATE_ADDED) <= _lastUpdate); + long curDateAddedSecs = getDateInSecs(c, colDATE_ADDED); + if (curDateAddedSecs > maxDateAddedSecs) { + maxDateAddedSecs = curDateAddedSecs; + } + isUpdate = (curDateAddedSecs <= filterLastUpdateMinInMillis / 1000); + + long curDateUpdatedSecs = getDateInSecs(c, colLAST_MODIFIED); + if (curDateUpdatedSecs > maxDateUpdatedSecs) { + maxDateUpdatedSecs = curDateUpdatedSecs; + } if (isUpdate) { updateCount++; lastSql = sqlUpdate; - isUpdate = bindAndExecUpdate(c, sqlUpdate) > 0; + isUpdate = bindAndExecUpdate(c, sqlUpdate, curDateAddedSecs, curDateUpdatedSecs) > 0; // 0 affected update rows: must insert } if (!isUpdate) { insertCout++; lastSql = sqlInsert; - bindAndExecInsert(c, sqlInsert); + bindAndExecInsert(c, sqlInsert, curDateAddedSecs, curDateUpdatedSecs); } lastSql = null; - // save(db, c, contentValues, _lastUpdate); + if ((progessListener != null) && (progress % 100) == 0) { if (!progessListener.onProgress(progress, itemCount, context.getString(R.string.scanner_update_result_format, progress))) { // canceled in gui thread @@ -651,8 +697,11 @@ public static int updateMediaCopy(Context context, SQLiteDatabase db, Date lastU } } progress++; - } + } // while over all old items db.setTransactionSuccessful(); // This commits the transaction if there were no exceptions + + saveStats(context, maxDateAddedSecs, maxDateUpdatedSecs); + if (Global.debugEnabledSql) { java.util.Date endTime = new java.util.Date(); final String message = "MediaDBRepository.updateMedaiCopy(inserted:" + insertCout + @@ -685,6 +734,25 @@ public static int updateMediaCopy(Context context, SQLiteDatabase db, Date lastU return progress; } + private static void saveStats(Context context, long maxDateAddedSecs, long maxDateUpdatedSecs) { + SharedPreferences prefsInstance = PreferenceManager + .getDefaultSharedPreferences(context.getApplicationContext()); + + SharedPreferences.Editor prefs = prefsInstance.edit(); + if (maxDateUpdatedSecs > 0) prefs.putLong("maxDateUpdatedSecs", maxDateUpdatedSecs + 1); + if (maxDateAddedSecs > 0) prefs.putLong("maxDateAddedSecs", maxDateAddedSecs + 1); + prefs.apply(); + } + + protected static long getDateInSecs(Cursor c, int colPosition) { + long curDateAdded = (c.isNull(colPosition)) ? 0 : c.getLong(colPosition); + if (curDateAdded > nextMonthTimeInSecs) { + // colDATE_ADDED: some apps/libs use milliscs instead of secs. Fix this. + curDateAdded = curDateAdded / 1000; + } + return curDateAdded; + } + private static void save(SQLiteDatabase db, Cursor c, ContentValues contentValues, long lastUpdate) { boolean isNew = (c.getLong(colDATE_ADDED) > lastUpdate); diff --git a/app/src/main/res/menu/menu_ao10.xml b/app/src/main/res/menu/menu_ao10.xml index f2749423..d8748856 100644 --- a/app/src/main/res/menu/menu_ao10.xml +++ b/app/src/main/res/menu/menu_ao10.xml @@ -7,4 +7,10 @@ android:title="@string/load_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 1e1e6dfd..cb1e9ff1 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -242,6 +242,7 @@ Verstecken kann über das \'Media-Scanner\' Gallery-Menü rückgängig gemacht w Dunkel Mediendatenbank (neu) laden + Mediendatenbank aktualisieren Fehlende Berechtigung Benötigt Verzeichnisberechtigungen. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f41a2453..4a442243 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -287,6 +287,7 @@ You can undo hiding by calling the mediascanner from gallery-menu." (Re)Load Media Database + Update Media Database Missing permisions