From 7b337020872154fc6bf74adc3e96d6dfe97c9869 Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Mon, 2 Dec 2019 05:21:47 +0100 Subject: [PATCH] #155: prepare to fix android10 incompatibility: Made CursorLoaderWithException swapable --- .../k3b/android/androFotoFinder/Global.java | 5 + .../backup/Backup2ZipService.java | 2 +- .../directory/DirectoryLoaderTask.java | 2 +- .../gallery/cursor/GalleryCursorFragment.java | 3 +- .../locationmap/MarkerLoaderTask.java | 2 +- .../queries/ContentProviderMediaImpl.java | 61 ++- .../queries/CursorLoaderWithException.java | 189 +++++++ .../queries/DatabaseHelper.java | 2 +- .../androFotoFinder/queries/FotoSql.java | 57 +- .../androFotoFinder/queries/FotoThumbSql.java | 2 +- .../androFotoFinder/queries/IMediaDBApi.java | 5 +- .../queries/MediaDBApiWrapper.java | 115 ++++ .../queries/MediaDBContentprovider.java | 13 +- .../queries/MediaImageDbReplacement.java | 507 +++++++++++++----- .../queries/SqlJobTaskBase.java | 2 +- .../android/androFotoFinder/tagDB/TagSql.java | 4 +- .../PhotoPropertiesMediaFilesScanner.java | 2 +- .../java/de/k3b/database/QueryParameter.java | 34 +- .../de/k3b/media/PhotoPropertiesWrapper.java | 1 - 19 files changed, 781 insertions(+), 227 deletions(-) create mode 100644 app/src/main/java/de/k3b/android/androFotoFinder/queries/CursorLoaderWithException.java create mode 100644 app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBApiWrapper.java diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/Global.java b/app/src/main/java/de/k3b/android/androFotoFinder/Global.java index c307438f..b654e478 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/Global.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/Global.java @@ -124,6 +124,11 @@ public static class Media { public static boolean initialImageDetailResolutionHigh = false; // false: MediaStore.Images.Thumbnails.MINI_KIND; true: FULL_SCREEN_KIND; public static boolean mapsForgeEnabled = false; + // #155: fix android10 incompatibility + // Build.VERSION_CODES.??ANDROID10?? = 29 + //!!! + public static final boolean useMediaImageDbReplacement = true; +// public static final boolean useMediaImageDbReplacement = (Build.VERSION.SDK_INT >= 29); /** map with blue selection markers: how much to area to increase */ public static final double mapMultiselectionBoxIncreaseByProcent = 100.0; /** map with blue selection markers: minimum size of zoom box in degrees */ diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/backup/Backup2ZipService.java b/app/src/main/java/de/k3b/android/androFotoFinder/backup/Backup2ZipService.java index fbf2c76e..8ca9daad 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/backup/Backup2ZipService.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/backup/Backup2ZipService.java @@ -211,7 +211,7 @@ private void execQuery(QueryParameter query, this.onProgress(0,0, "Calculate"); cursor = FotoSql.getMediaDBApi().createCursorForQuery( null, "ZipExecute", - query, null); + query, null, null); int itemCount = cursor.getCount(); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryLoaderTask.java b/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryLoaderTask.java index eff46449..92ebcd62 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryLoaderTask.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryLoaderTask.java @@ -103,7 +103,7 @@ protected IDirectory doInBackground(QueryParameter... queryParameter) { try { cursor = FotoSql.getMediaDBApi().createCursorForQuery( null, "ZipExecute", - queryParameters, null); + queryParameters, null, null); int itemCount = cursor.getCount(); final int expectedCount = itemCount + itemCount; diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/gallery/cursor/GalleryCursorFragment.java b/app/src/main/java/de/k3b/android/androFotoFinder/gallery/cursor/GalleryCursorFragment.java index 0b228046..02696b3f 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/gallery/cursor/GalleryCursorFragment.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/gallery/cursor/GalleryCursorFragment.java @@ -71,6 +71,7 @@ import de.k3b.android.androFotoFinder.imagedetail.ImageDetailMetaDialogBuilder; import de.k3b.android.androFotoFinder.locationmap.GeoEditActivity; import de.k3b.android.androFotoFinder.locationmap.MapGeoPickerActivity; +import de.k3b.android.androFotoFinder.queries.CursorLoaderWithException; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.androFotoFinder.queries.FotoViewerParameter; import de.k3b.android.androFotoFinder.queries.Queryable; @@ -231,7 +232,7 @@ public void onLoadFinished(Loader _loader, Cursor data) { final Activity context = getActivity(); if (data == null) { - FotoSql.CursorLoaderWithException loader = (FotoSql.CursorLoaderWithException) _loader; + CursorLoaderWithException loader = (CursorLoaderWithException) _loader; String title; String message = context.getString(R.string.global_err_sql_message_format, loader.getException().getMessage(), loader.getQuery().toSqlString()); if (loader.getException() != null) { diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/MarkerLoaderTask.java b/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/MarkerLoaderTask.java index 7ba13187..8e8ba0c7 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/MarkerLoaderTask.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/MarkerLoaderTask.java @@ -97,7 +97,7 @@ protected OverlayManager doInBackground(QueryParameter... queryParameter) { try { cursor = FotoSql.getMediaDBApi().createCursorForQuery( null, "MakerLoader", - queryParameters, null); + queryParameters, null, null); int itemCount = cursor.getCount(); final int expectedCount = itemCount + itemCount; diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/ContentProviderMediaImpl.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/ContentProviderMediaImpl.java index 45e6f3f7..8e8038b8 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/ContentProviderMediaImpl.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/ContentProviderMediaImpl.java @@ -25,6 +25,8 @@ import android.database.Cursor; import android.database.DatabaseUtils; import android.net.Uri; +import android.os.Build; +import android.os.CancellationSignal; import android.util.Log; import java.util.ArrayList; @@ -39,35 +41,44 @@ * Static Implementation of Context.getContentResolver()-ContentProvider based media api */ public class ContentProviderMediaImpl { + private static final String MODUL_NAME = ContentProviderMediaImpl.class.getName(); + public static Cursor createCursorForQuery( StringBuilder out_debugMessage, String dbgContext, final Context context, - QueryParameter parameters, VISIBILITY visibility) { + QueryParameter parameters, VISIBILITY visibility, CancellationSignal cancellationSignal) { if (visibility != null) FotoSql.setWhereVisibility(parameters, visibility); return createCursorForQuery(out_debugMessage, dbgContext, context, parameters.toFrom(), parameters.toAndroidWhere(), parameters.toAndroidParameters(), parameters.toOrderBy(), - parameters.toColumns() + cancellationSignal, parameters.toColumns() ); } /** * every cursor query should go through this. adds logging if enabled */ - static Cursor createCursorForQuery(StringBuilder out_debugMessage, String dbgContext, final Context context, final String from, final String sqlWhereStatement, - final String[] sqlWhereParameters, final String sqlSortOrder, - final String... sqlSelectColums) { + static Cursor createCursorForQuery( + StringBuilder out_debugMessage, String dbgContext, final Context context, + final String from, final String sqlWhereStatement, + final String[] sqlWhereParameters, final String sqlSortOrder, + CancellationSignal cancellationSignal, final String... sqlSelectColums) { ContentResolver resolver = context.getContentResolver(); Cursor query = null; Exception excpetion = null; try { - query = resolver.query(Uri.parse(from), sqlSelectColums, sqlWhereStatement, sqlWhereParameters, sqlSortOrder); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + query = resolver.query(Uri.parse(from), sqlSelectColums, sqlWhereStatement, sqlWhereParameters, sqlSortOrder, cancellationSignal); + } else { + query = resolver.query(Uri.parse(from), sqlSelectColums, sqlWhereStatement, sqlWhereParameters, sqlSortOrder); + } } catch (Exception ex) { excpetion = ex; } finally { if ((excpetion != null) || Global.debugEnabledSql || (out_debugMessage != null)) { StringBuilder message = StringUtils.appendMessage(out_debugMessage, excpetion, - dbgContext, "FotoSql.createCursorForQuery:\n", + dbgContext, MODUL_NAME + + ".createCursorForQuery:\n", QueryParameter.toString(sqlSelectColums, null, from, sqlWhereStatement, sqlWhereParameters, sqlSortOrder, query.getCount())); if (out_debugMessage == null) { @@ -79,10 +90,6 @@ static Cursor createCursorForQuery(StringBuilder out_debugMessage, String dbgCon return query; } - public static int execUpdate(String dbgContext, Context context, long id, ContentValues values) { - return exexUpdateImpl(dbgContext, context, values, FotoSql.FILTER_COL_PK, new String[]{Long.toString(id)}); - } - public static int execUpdate(String dbgContext, Context context, String path, ContentValues values, VISIBILITY visibility) { return exexUpdateImpl(dbgContext, context, values, FotoSql.getFilterExprPathLikeWithVisibility(visibility), new String[]{path}); } @@ -101,7 +108,9 @@ public static int exexUpdateImpl(String dbgContext, Context context, ContentValu excpetion = ex; } finally { if ((excpetion != null) || ((dbgContext != null) && (Global.debugEnabledSql || LibGlobal.debugEnabledJpg))) { - Log.i(Global.LOG_CONTEXT, dbgContext + ":FotoSql.exexUpdate " + excpetion + "\n" + + Log.i(Global.LOG_CONTEXT, dbgContext + ":" + + MODUL_NAME + + ".exexUpdate " + excpetion + "\n" + QueryParameter.toString(null, values.toString(), FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, sqlWhere, selectionArgs, null, result), excpetion); } @@ -146,7 +155,9 @@ public static Uri execInsert(String dbgContext, Context context, ContentValues v excpetion = ex; } finally { if ((excpetion != null) || Global.debugEnabledSql || LibGlobal.debugEnabledJpg) { - Log.i(Global.LOG_CONTEXT, dbgContext + ":FotoSql.execInsert " + excpetion + " " + + Log.i(Global.LOG_CONTEXT, dbgContext + ":" + + MODUL_NAME + + ".execInsert " + excpetion + " " + values.toString() + " => " + result + " " + excpetion, excpetion); } } @@ -166,7 +177,9 @@ public static int deleteMedia(String dbgContext, Context context, String where, ContentValues values = new ContentValues(); values.put(FotoSql.SQL_COL_PATH, FotoSql.DELETED_FILE_MARKER); values.put(FotoSql.SQL_COL_EXT_MEDIA_TYPE, 0); // so it will not be shown as image any more - exexUpdateImpl(dbgContext + "-a: FotoSql.deleteMedia: ", + exexUpdateImpl(dbgContext + "-a: " + + MODUL_NAME + + ".deleteMedia: ", context, values, lastUsedWhereClause, lastSelectionArgs); lastUsedWhereClause = FotoSql.SQL_COL_PATH + " is null"; @@ -174,7 +187,9 @@ public static int deleteMedia(String dbgContext, Context context, String where, delCount = context.getContentResolver() .delete(FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE, lastUsedWhereClause, lastSelectionArgs); if (Global.debugEnabledSql || LibGlobal.debugEnabledJpg) { - Log.i(Global.LOG_CONTEXT, dbgContext + "-b: FotoSql.deleteMedia delete\n" + + Log.i(Global.LOG_CONTEXT, dbgContext + "-b: " + + MODUL_NAME + + ".deleteMedia delete\n" + QueryParameter.toString(null, null, FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, lastUsedWhereClause, lastSelectionArgs, null, delCount)); } @@ -182,7 +197,9 @@ public static int deleteMedia(String dbgContext, Context context, String where, delCount = context.getContentResolver() .delete(FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE, lastUsedWhereClause, lastSelectionArgs); if (Global.debugEnabledSql || LibGlobal.debugEnabledJpg) { - Log.i(Global.LOG_CONTEXT, dbgContext + ": FotoSql.deleteMedia\ndelete " + + Log.i(Global.LOG_CONTEXT, dbgContext + ": " + + MODUL_NAME + + ".deleteMedia\ndelete " + QueryParameter.toString(null, null, FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, lastUsedWhereClause, lastSelectionArgs, null, delCount)); @@ -190,7 +207,9 @@ public static int deleteMedia(String dbgContext, Context context, String where, } } catch (Exception ex) { // null pointer exception when delete matches not items?? - final String msg = dbgContext + ": Exception in FotoSql.deleteMedia:\n" + + final String msg = dbgContext + ": Exception in " + + MODUL_NAME + + ".deleteMedia:\n" + QueryParameter.toString(null, null, FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, lastUsedWhereClause, lastSelectionArgs, null, -1) + " : " + ex.getMessage(); @@ -207,7 +226,8 @@ public static int deleteMedia(String dbgContext, Context context, String where, * @return number of updated items */ private static int _del_execRenameFolder_batch_not_working(Context context, String pathOld, String pathNew) { - final String dbgContext = "FotoSql.execRenameFolder('" + + final String dbgContext = MODUL_NAME + + ".execRenameFolder('" + pathOld + "' => '" + pathNew + "')"; // sql update file set path = newBegin + substing(path, begin+len) where path like newBegin+'%' // public static final String SQL_EXPR_FOLDER = "substr(" + SQL_COL_PATH + ",1,length(" + SQL_COL_PATH + ") - length(" + MediaStore.Images.Media.DISPLAY_NAME + "))"; @@ -229,7 +249,7 @@ private static int _del_execRenameFolder_batch_not_working(Context context, Stri Cursor c = null; try { - c = createCursorForQuery(null, dbgContext, context, queryAffectedFiles, null); + c = createCursorForQuery(null, dbgContext, context, queryAffectedFiles, null, null); int pkColNo = c.getColumnIndex(FotoSql.SQL_COL_PK); int pathColNo = c.getColumnIndex(sqlColNewPathAlias); @@ -271,7 +291,8 @@ public static ContentValues getDbContent(Context context, final long id) { return values; } } catch (Exception ex) { - Log.e(Global.LOG_CONTEXT, "FotoSql.getDbContent(id=" + id + ") failed", ex); + Log.e(Global.LOG_CONTEXT, MODUL_NAME + + ".getDbContent(id=" + id + ") failed", ex); } finally { if (c != null) c.close(); } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/CursorLoaderWithException.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/CursorLoaderWithException.java new file mode 100644 index 00000000..4c68bd79 --- /dev/null +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/CursorLoaderWithException.java @@ -0,0 +1,189 @@ +package de.k3b.android.androFotoFinder.queries; + +import android.content.AsyncTaskLoader; +import android.content.Context; +import android.database.Cursor; +import android.os.Build; +import android.os.CancellationSignal; +import android.os.OperationCanceledException; +import android.util.Log; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +import de.k3b.android.androFotoFinder.Global; +import de.k3b.database.QueryParameter; + +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Copied from android.content.CursorLoaderWithException + */ +public class CursorLoaderWithException extends AsyncTaskLoader { + final ForceLoadContentObserver mObserver; + + private final QueryParameter query; + Cursor mCursor; + CancellationSignal mCancellationSignal; + private Exception mException; + + public CursorLoaderWithException(Context context, QueryParameter query) { + super(context); + this.query = query; + mObserver = new ForceLoadContentObserver(); + } + + /* Runs on a worker thread */ + @Override + public Cursor loadInBackground() { + mException = null; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + synchronized (this) { + if (isLoadInBackgroundCanceled()) { + throw new OperationCanceledException(); + } + mCancellationSignal = new CancellationSignal(); + } + } + try { + Cursor cursor; + + cursor = FotoSql.getMediaDBApi().createCursorForQuery(null, "loadader", this.query, null, mCancellationSignal); + if (cursor != null) { + try { + // Ensure the cursor window is filled. + cursor.getCount(); + cursor.registerContentObserver(mObserver); + } catch (RuntimeException ex) { + cursor.close(); + throw ex; + } + } + return cursor; + } catch (Exception ex) { + final String msg = "FotoSql.createCursorLoader()#loadInBackground failed:\n\t" + query.toSqlString(); + Log.e(Global.LOG_CONTEXT, msg, ex); + mException = ex; + return null; + } finally { + synchronized (this) { + mCancellationSignal = null; + } + } + } + + @Override + public void cancelLoadInBackground() { + super.cancelLoadInBackground(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + synchronized (this) { + if (mCancellationSignal != null) { + mCancellationSignal.cancel(); + } + } + } + } + + /* Runs on the UI thread */ + @Override + public void deliverResult(Cursor cursor) { + if (isReset()) { + // An async query came in while the loader is stopped + if (cursor != null) { + cursor.close(); + } + return; + } + Cursor oldCursor = mCursor; + mCursor = cursor; + + if (isStarted()) { + super.deliverResult(cursor); + } + + if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) { + oldCursor.close(); + } + } + + /** + * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks + * will be called on the UI thread. If a previous load has been completed and is still valid + * the result may be passed to the callbacks immediately. + *

+ * Must be called from the UI thread + */ + @Override + protected void onStartLoading() { + if (mCursor != null) { + deliverResult(mCursor); + } + if (takeContentChanged() || mCursor == null) { + forceLoad(); + } + } + + /** + * Must be called from the UI thread + */ + @Override + protected void onStopLoading() { + // Attempt to cancel the current load task if possible. + cancelLoad(); + } + + @Override + public void onCanceled(Cursor cursor) { + if (cursor != null && !cursor.isClosed()) { + cursor.close(); + } + } + + @Override + protected void onReset() { + super.onReset(); + + // Ensure the loader is stopped + onStopLoading(); + + if (mCursor != null && !mCursor.isClosed()) { + mCursor.close(); + } + mCursor = null; + } + + public QueryParameter getQuery() { + return query; + } + + public Exception getException() { + return mException; + } + + @Override + public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { + super.dump(prefix, fd, writer, args); + writer.print(prefix); + writer.print("query="); + writer.println(this.query.toSqlString()); + writer.print(prefix); + writer.print("mCursor="); + writer.println(mCursor); + } +} 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 f33f5ae7..76150770 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 @@ -74,7 +74,7 @@ public void onUpgrade(final SQLiteDatabase db, final int oldVersion, private static DatabaseHelper instance = null; private void version2Upgrade_MediDbCopy(final SQLiteDatabase db) { - for (String sql : MediaImageDbReplacement.DDL) { + for (String sql : MediaImageDbReplacement.Impl.DDL) { db.execSQL(sql); } } 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 6196bc98..f90cc2e2 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 @@ -21,7 +21,6 @@ import android.content.ContentValues; import android.content.Context; -import android.content.CursorLoader; import android.database.Cursor; import android.net.Uri; import android.os.Build; @@ -773,7 +772,7 @@ public static String execGetFotoPath(Uri uriWithID) { uriWithID.toString(), null, null, null, - FotoSql.SQL_COL_PATH); + null, FotoSql.SQL_COL_PATH); if (c.moveToFirst()) { return DBUtils.getString(c,FotoSql.SQL_COL_PATH, null); } @@ -799,7 +798,7 @@ public static List execGetFotoPaths(String pathFilter) { c = mediaDBApi.createCursorForQuery( null, "execGetFotoPaths(pathFilter)", - query, null); + query, null, null); while (c.moveToNext()) { result.add(c.getString(0)); } @@ -844,7 +843,7 @@ public static IGeoRectangle execGetGeoRectangle(StringBuilder out_debugMessage, GeoRectangle result = null; Cursor c = null; try { - c = mediaDBApi.createCursorForQuery(debugMessage, "execGetGeoRectangle", query, VISIBILITY.PRIVATE_PUBLIC); + c = mediaDBApi.createCursorForQuery(debugMessage, "execGetGeoRectangle", query, VISIBILITY.PRIVATE_PUBLIC, null); if (c.moveToFirst()) { result = new GeoRectangle(); result.setLatitude(c.getDouble(0), c.getDouble(1)); @@ -888,7 +887,7 @@ public static IGeoPoint execGetPosition(StringBuilder out_debugMessage, GeoPoint result = null; Cursor c = null; try { - c = mediaDBApi.createCursorForQuery(debugMessage, "execGetPosition", query, VISIBILITY.PRIVATE_PUBLIC); + c = mediaDBApi.createCursorForQuery(debugMessage, "execGetPosition", query, VISIBILITY.PRIVATE_PUBLIC, null); if (c.moveToFirst()) { result = new GeoPoint(c.getDouble(0),c.getDouble(1)); return result; @@ -923,7 +922,7 @@ public static Map execGetPathIdMap(String... fileNames) { Cursor c = null; try { - c = mediaDBApi.createCursorForQuery(null, "execGetPathIdMap", query, null); + c = mediaDBApi.createCursorForQuery(null, "execGetPathIdMap", query, null, null); while (c.moveToNext()) { result.put(c.getString(1),c.getLong(0)); } @@ -1010,9 +1009,9 @@ protected static String getFilterExprPathLikeWithVisibility(VISIBILITY visibilit } @NonNull - public static CursorLoader createCursorLoader(Context context, final QueryParameter query) { + public static CursorLoaderWithException createCursorLoader(Context context, final QueryParameter query) { FotoSql.setWhereVisibility(query, VISIBILITY.DEFAULT); - final CursorLoader loader = new CursorLoaderWithException(context, query); + final CursorLoaderWithException loader = new CursorLoaderWithException(context, query); return loader; } @@ -1102,7 +1101,7 @@ public static String getMinFolder(QueryParameter query, Cursor c = null; try { - c = mediaDBApi.createCursorForQuery(null, "getCount", queryModified, null); + c = mediaDBApi.createCursorForQuery(null, "getCount", queryModified, null, null); if (c.moveToNext()) { return c.getString(0); } @@ -1121,7 +1120,7 @@ public static long getCount(QueryParameter query) { Cursor c = null; try { - c = mediaDBApi.createCursorForQuery(null, "getCount", queryModified, null); + c = mediaDBApi.createCursorForQuery(null, "getCount", queryModified, null, null); if (c.moveToNext()) { return c.getLong(0); } @@ -1150,7 +1149,7 @@ public static CharSequence getStatisticsMessage(Context context, int prefixStrin Cursor c = null; try { - c = mediaDBApi.createCursorForQuery(null, "getCount", queryModified, null); + c = mediaDBApi.createCursorForQuery(null, "getCount", queryModified, null, null); if (c.moveToNext()) { final long count = c.getLong(0); long size = c.getLong(1); @@ -1186,7 +1185,7 @@ private static SelectedFiles getSelectedfiles(QueryParameter query, String colna Cursor c = null; try { - c = mediaDBApi.createCursorForQuery(null, "getSelectedfiles", query, visibility); + c = mediaDBApi.createCursorForQuery(null, "getSelectedfiles", query, visibility, null); int len = c.getCount(); Long[] ids = new Long[len]; String[] paths = new String[len]; @@ -1260,7 +1259,7 @@ private static List getFileNamesImpl(QueryParameter parameters, List + */ +package de.k3b.android.androFotoFinder.queries; + +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.os.CancellationSignal; + +import de.k3b.database.QueryParameter; +import de.k3b.io.VISIBILITY; +import de.k3b.media.IPhotoProperties; + +/** + * (Default) Implementation of {@link IMediaDBApi} to forward all methods to an inner child {@link IPhotoProperties}. + *

+ * Created by k3b on 30.11.2019. + */ +public class MediaDBApiWrapper implements IMediaDBApi { + protected final IMediaDBApi readChild; + protected final IMediaDBApi writeChild; + + /** + * count the non path write calls + */ + private int modifyCount = 0; + + public MediaDBApiWrapper(IMediaDBApi child) { + this(child, child); + } + + public MediaDBApiWrapper(IMediaDBApi readChild, IMediaDBApi writeChild) { + this.readChild = readChild; + this.writeChild = writeChild; + } + + @Override + public Cursor createCursorForQuery(StringBuilder out_debugMessage, String dbgContext, QueryParameter parameters, VISIBILITY visibility, CancellationSignal cancellationSignal) { + return readChild.createCursorForQuery(out_debugMessage, dbgContext, parameters, visibility, cancellationSignal); + } + + @Override + public Cursor createCursorForQuery(StringBuilder out_debugMessage, String dbgContext, String from, String sqlWhereStatement, String[] sqlWhereParameters, String sqlSortOrder, CancellationSignal cancellationSignal, String... sqlSelectColums) { + return readChild.createCursorForQuery(out_debugMessage, dbgContext, from, sqlWhereStatement, sqlWhereParameters, sqlSortOrder, cancellationSignal, sqlSelectColums); + } + + @Override + public int execUpdate(String dbgContext, long id, ContentValues values) { + return writeChild.execUpdate(dbgContext, id, values); + } + + @Override + public int execUpdate(String dbgContext, String path, ContentValues values, VISIBILITY visibility) { + return writeChild.execUpdate(dbgContext, path, values, visibility); + } + + @Override + public int exexUpdateImpl(String dbgContext, ContentValues values, String sqlWhere, String[] selectionArgs) { + return writeChild.exexUpdateImpl(dbgContext, values, sqlWhere, selectionArgs); + } + + /** + * return id of inserted item + * + * @param dbgContext + * @param dbUpdateFilterJpgFullPathName + * @param values + * @param visibility + * @param updateSuccessValue + */ + @Override + public Long insertOrUpdateMediaDatabase(String dbgContext, String dbUpdateFilterJpgFullPathName, ContentValues values, VISIBILITY visibility, Long updateSuccessValue) { + return writeChild.insertOrUpdateMediaDatabase(dbgContext, dbUpdateFilterJpgFullPathName, values, visibility, updateSuccessValue); + } + + /** + * every database insert should go through this. adds logging if enabled + * + * @param dbgContext + * @param values + */ + @Override + public Uri execInsert(String dbgContext, ContentValues values) { + return writeChild.execInsert(dbgContext, values); + } + + /** + * Deletes media items specified by where with the option to prevent cascade delete of the image. + */ + @Override + public int deleteMedia(String dbgContext, String where, String[] selectionArgs, boolean preventDeleteImageFile) { + return writeChild.deleteMedia(dbgContext, where, selectionArgs, preventDeleteImageFile); + } + + @Override + public ContentValues getDbContent(long id) { + return readChild.getDbContent(id); + } +} diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBContentprovider.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBContentprovider.java index 645a6078..1e80da62 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBContentprovider.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBContentprovider.java @@ -22,6 +22,7 @@ import android.content.Context; import android.database.Cursor; import android.net.Uri; +import android.os.CancellationSignal; import de.k3b.database.QueryParameter; import de.k3b.io.VISIBILITY; @@ -39,28 +40,28 @@ public MediaDBContentprovider(final Context context) { @Override public Cursor createCursorForQuery( StringBuilder out_debugMessage, String dbgContext, - QueryParameter parameters, VISIBILITY visibility) { + QueryParameter parameters, VISIBILITY visibility, CancellationSignal cancellationSignal) { return ContentProviderMediaImpl.createCursorForQuery( - out_debugMessage, dbgContext, context, parameters, visibility); + out_debugMessage, dbgContext, context, parameters, visibility, cancellationSignal); } @Override public Cursor createCursorForQuery(StringBuilder out_debugMessage, String dbgContext, final String from, final String sqlWhereStatement, final String[] sqlWhereParameters, final String sqlSortOrder, - final String... sqlSelectColums) { + CancellationSignal cancellationSignal, final String... sqlSelectColums) { return ContentProviderMediaImpl.createCursorForQuery( out_debugMessage, dbgContext, context, from, sqlWhereStatement, - sqlWhereParameters, sqlSortOrder, sqlSelectColums); + sqlWhereParameters, sqlSortOrder, null, sqlSelectColums); } @Override public int execUpdate(String dbgContext, long id, ContentValues values) { - return ContentProviderMediaImpl.execUpdate(dbgContext, context, id, values); + return exexUpdateImpl(dbgContext, values, FotoSql.FILTER_COL_PK, new String[]{Long.toString(id)}); } @Override public int execUpdate(String dbgContext, String path, ContentValues values, VISIBILITY visibility) { - return ContentProviderMediaImpl.execUpdate(dbgContext, context, path, values, visibility); + return exexUpdateImpl(dbgContext, values, FotoSql.getFilterExprPathLikeWithVisibility(visibility), new String[]{path}); } @Override diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaImageDbReplacement.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaImageDbReplacement.java index c059ae32..855f2029 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaImageDbReplacement.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaImageDbReplacement.java @@ -21,14 +21,22 @@ import android.content.ContentValues; import android.content.Context; import android.database.Cursor; +import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.os.Build; +import android.os.CancellationSignal; import android.provider.MediaStore; +import android.util.Log; import java.sql.Date; +import de.k3b.LibGlobal; import de.k3b.android.androFotoFinder.Global; import de.k3b.database.QueryParameter; import de.k3b.io.AlbumFile; +import de.k3b.io.StringUtils; +import de.k3b.io.VISIBILITY; import static de.k3b.android.androFotoFinder.queries.FotoSql.QUERY_TYPE_UNDEFINED; import static de.k3b.android.androFotoFinder.queries.FotoSql.SQL_COL_DATE_ADDED; @@ -53,167 +61,398 @@ * Since Android-10 (api 29) using sqLite functions as content-provider-columns is not possible anymore. * Therefore apm uses a copy of contentprovider MediaStore.Images with same column names. */ -public class MediaImageDbReplacement { +public class MediaImageDbReplacement implements IMediaDBApi { + private static final String MODUL_NAME = ContentProviderMediaImpl.class.getName(); + private final SQLiteDatabase db; + + public MediaImageDbReplacement(SQLiteDatabase db) { + this.db = db; + } + + @Override + public Cursor createCursorForQuery(StringBuilder out_debugMessage, String dbgContext, + QueryParameter parameters, VISIBILITY visibility, + CancellationSignal cancellationSignal) { + if (visibility != null) FotoSql.setWhereVisibility(parameters, visibility); + return createCursorForQuery(out_debugMessage, dbgContext, + parameters.toWhere(), parameters.toAndroidParameters(), + parameters.toGroupBy(), parameters.toHaving(), + parameters.toOrderBy(), + cancellationSignal, parameters.toColumns() + ); + } + + @Override + public Cursor createCursorForQuery(StringBuilder out_debugMessage, String dbgContext, String from, + String sqlWhereStatement, String[] sqlWhereParameters, + String sqlSortOrder, CancellationSignal cancellationSignal, + String... sqlSelectColums) { + return createCursorForQuery(out_debugMessage, dbgContext, + sqlWhereStatement, sqlWhereParameters, + null, null, + sqlSortOrder, cancellationSignal, sqlSelectColums); + } + /** - * SQL to create copy of contentprovider MediaStore.Images. - * copied from android-4.4 android database. Removed columns not used + * every cursor query should go through this. adds logging if enabled */ - public static final String[] DDL = new String[]{ - "CREATE TABLE \"files\" (\n" + - "\t_id INTEGER PRIMARY KEY AUTOINCREMENT,\n" + - "\t_size INTEGER,\n" + - "\tdate_added INTEGER,\n" + - "\tdate_modified INTEGER,\n" + - "\tdatetaken INTEGER,\n" + - "\torientation INTEGER,\n" + - "\tduration INTEGER,\n" + - "\tbookmark INTEGER,\n" + - "\tmedia_type INTEGER,\n" + - "\twidth INTEGER,\n" + - "\theight INTEGER,\n" + - - "\t_data TEXT UNIQUE COLLATE NOCASE,\n" + - "\ttitle TEXT,\n" + - "\tdescription TEXT,\n" + - "\t_display_name TEXT,\n" + - "\tmime_type TEXT,\n" + - "\ttags TEXT,\n" + - - "\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 titlekey_index ON files(title_key)", - "CREATE INDEX media_type_index ON files(media_type)", - }; - - public static final String table = "files"; - // same colum order as in DDL - private static final String[] USED_MEDIA_COLUMNS = new String[]{ - // INTEGER 0 .. 10 - SQL_COL_PK, - SQL_COL_DATE_ADDED, - SQL_COL_LAST_MODIFIED, - SQL_COL_SIZE, - SQL_COL_DATE_TAKEN, - SQL_COL_ORIENTATION, - SQL_COL_EXT_XMP_LAST_MODIFIED_DATE, // duration - SQL_COL_EXT_RATING, // bookmark - SQL_COL_EXT_MEDIA_TYPE, - MediaStore.MediaColumns.WIDTH, - MediaStore.MediaColumns.HEIGHT, - - // TEXT 11 .. 16 - SQL_COL_PATH, // _data - SQL_COL_EXT_TITLE, - SQL_COL_EXT_DESCRIPTION, - SQL_COL__IMPL_DISPLAY_NAME, - MediaStore.MediaColumns.MIME_TYPE, - SQL_COL_EXT_TAGS, - - // DOUBLE 17..18 - SQL_COL_LAT, - SQL_COL_LON, - }; - - private static final int intMin = 0; - private static final int intMax = 10; - private static final int txtMin = 11; - private static final int txtMax = 16; - private static final int dblMin = 17; - private static final int dblMax = 18; - - private static final int colID = 0; - private static final int colDATE_ADDED = 1; - private static final int colLAST_MODIFIED = 2; - private static final String FILTER_EXPR_AFFECTED_FILES - = "(" + FotoSql.FILTER_EXPR_PRIVATE_PUBLIC - + " OR " + SQL_COL_PATH + " like '%" + AlbumFile.SUFFIX_VALBUM + "' " - + " OR " + SQL_COL_PATH + " like '%" + AlbumFile.SUFFIX_QUERY + "' " - + ")"; - private static final QueryParameter queryGetAllColumns = new QueryParameter() - .setID(QUERY_TYPE_UNDEFINED) - .addColumn(USED_MEDIA_COLUMNS) - .addFrom(SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME) - .addWhere(FILTER_EXPR_AFFECTED_FILES); - - private static boolean isLomg(int index) { - return index >= intMin && index <= intMax; + private Cursor createCursorForQuery(StringBuilder out_debugMessage, String dbgContext, + String sqlWhereStatement, String[] selectionArgs, String groupBy, + String having, String sqlSortOrder, + CancellationSignal cancellationSignal, final String... sqlSelectColums) { + Cursor query = null; + + Exception excpetion = null; + try { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + query = db.query(false, Impl.table, sqlSelectColums, sqlWhereStatement, selectionArgs, + groupBy, having, sqlSortOrder, null, cancellationSignal); + } else { + query = db.query(false, Impl.table, sqlSelectColums, sqlWhereStatement, selectionArgs, + groupBy, having, sqlSortOrder, null); + } + + } catch (Exception ex) { + excpetion = ex; + } finally { + if ((excpetion != null) || Global.debugEnabledSql || (out_debugMessage != null)) { + StringBuilder message = StringUtils.appendMessage(out_debugMessage, excpetion, + dbgContext, MODUL_NAME + + ".createCursorForQuery:\n", + QueryParameter.toString(sqlSelectColums, null, Impl.table, sqlWhereStatement, + selectionArgs, sqlSortOrder, query.getCount())); + if (out_debugMessage == null) { + Log.i(Global.LOG_CONTEXT, message.toString(), excpetion); + } // else logging is done by caller + } + } + + return query; } - // private Object get(Cursor cursor, columIndex) + @Override + public int execUpdate(String dbgContext, long id, ContentValues values) { + return exexUpdateImpl(dbgContext, values, FotoSql.FILTER_COL_PK, new String[]{Long.toString(id)}); + } - private static boolean isString(int index) { - return index >= txtMin && index <= txtMax; + @Override + public int execUpdate(String dbgContext, String path, ContentValues values, VISIBILITY visibility) { + return exexUpdateImpl(dbgContext, values, FotoSql.getFilterExprPathLikeWithVisibility(visibility), new String[]{path}); } - private static boolean isDouble(int index) { - return index >= dblMin && index <= dblMax; + @Override + public int exexUpdateImpl(String dbgContext, ContentValues values, String sqlWhere, String[] selectionArgs) { + int result = -1; + Exception excpetion = null; + try { + result = db.update(Impl.table, values, sqlWhere, selectionArgs); + } catch (Exception ex) { + excpetion = ex; + } finally { + if ((excpetion != null) || ((dbgContext != null) && (Global.debugEnabledSql || LibGlobal.debugEnabledJpg))) { + Log.i(Global.LOG_CONTEXT, dbgContext + ":" + + MODUL_NAME + + ".exexUpdate " + excpetion + "\n" + + QueryParameter.toString(null, values.toString(), FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, + sqlWhere, selectionArgs, null, result), excpetion); + } + } + return result; } - private static ContentValues getContentValues(Cursor cursor, ContentValues destination) { - destination.clear(); - int colCount = cursor.getColumnCount(); - String columnName; - for (int i = 0; i < colCount; i++) { - columnName = cursor.getColumnName(i); - if (cursor.isNull(i)) { - destination.putNull(columnName); - } else if (isLomg(i)) { - destination.put(columnName, cursor.getLong(i)); - } else if (isString(i)) { - destination.put(columnName, cursor.getString(i)); - } else if (isDouble(i)) { - destination.put(columnName, cursor.getDouble(i)); + /** + * return id of inserted item + * + * @param dbgContext + * @param dbUpdateFilterJpgFullPathName + * @param values + * @param visibility + * @param updateSuccessValue + */ + @Override + public Long insertOrUpdateMediaDatabase(String dbgContext, String dbUpdateFilterJpgFullPathName, + ContentValues values, VISIBILITY visibility, Long updateSuccessValue) { + Long result = updateSuccessValue; + + int modifyCount = execUpdate(dbgContext, dbUpdateFilterJpgFullPathName, + values, visibility); + + if (modifyCount == 0) { + // update failed (probably becauce oldFullPathName not found. try insert it. + FotoSql.addDateAdded(values); + + Uri uriWithId = execInsert(dbgContext, values); + result = FotoSql.getId(uriWithId); + } + return result; + } + + /** + * every database insert should go through this. adds logging if enabled + * + * @param dbgContext + * @param values + */ + @Override + public Uri execInsert(String dbgContext, ContentValues values) { + long result = 0; + Exception excpetion = null; + try { + // on my android-4.4 insert with media_type=1001 (private) does insert with media_type=1 (image) + result = db.insert(Impl.table, null, values); + } catch (Exception ex) { + excpetion = ex; + } finally { + if ((excpetion != null) || Global.debugEnabledSql || LibGlobal.debugEnabledJpg) { + Log.i(Global.LOG_CONTEXT, dbgContext + ":" + + MODUL_NAME + + ".execInsert " + excpetion + " " + + values.toString() + " => " + result + " " + excpetion, excpetion); } } - return destination; + return Uri.parse("content://apm/photo/" + result); } - public static int updateMedaiCopy(Context context, SQLiteDatabase db, Date lastUpdate) { - int changeCount = 0; + /** + * Deletes media items specified by where with the option to prevent cascade delete of the image. + * + * @param dbgContext + * @param where + * @param selectionArgs + * @param preventDeleteImageFile + */ + @Override + public int deleteMedia(String dbgContext, String where, String[] selectionArgs, boolean preventDeleteImageFile) { + String[] lastSelectionArgs = selectionArgs; + String lastUsedWhereClause = where; + int delCount = 0; + try { + if (preventDeleteImageFile) { + // set SQL_COL_PATH empty so sql-delete cannot cascade delete the referenced image-file via delete trigger + ContentValues values = new ContentValues(); + values.put(FotoSql.SQL_COL_PATH, FotoSql.DELETED_FILE_MARKER); + values.put(FotoSql.SQL_COL_EXT_MEDIA_TYPE, 0); // so it will not be shown as image any more + exexUpdateImpl(dbgContext + "-a: " + + MODUL_NAME + + ".deleteMedia: ", + values, lastUsedWhereClause, lastSelectionArgs); - QueryParameter query = queryGetAllColumns; - long _lastUpdate = (lastUpdate != null) ? (lastUpdate.getTime() / 1000L) : 0L; + lastUsedWhereClause = FotoSql.SQL_COL_PATH + " is null"; + lastSelectionArgs = null; + delCount = db.delete(Impl.table, lastUsedWhereClause, lastSelectionArgs); + if (Global.debugEnabledSql || LibGlobal.debugEnabledJpg) { + Log.i(Global.LOG_CONTEXT, dbgContext + "-b: " + + MODUL_NAME + + ".deleteMedia delete\n" + + QueryParameter.toString(null, null, FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, + lastUsedWhereClause, lastSelectionArgs, null, delCount)); + } + } else { + delCount = db.delete(Impl.table, lastUsedWhereClause, lastSelectionArgs); + if (Global.debugEnabledSql || LibGlobal.debugEnabledJpg) { + Log.i(Global.LOG_CONTEXT, dbgContext + ": " + + MODUL_NAME + + ".deleteMedia\ndelete " + + QueryParameter.toString(null, null, + FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, + lastUsedWhereClause, lastSelectionArgs, null, delCount)); + } + } + } catch (Exception ex) { + // null pointer exception when delete matches not items?? + final String msg = dbgContext + ": Exception in " + + MODUL_NAME + + ".deleteMedia:\n" + + QueryParameter.toString(null, null, FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, + lastUsedWhereClause, lastSelectionArgs, null, -1) + + " : " + ex.getMessage(); + Log.e(Global.LOG_CONTEXT, msg, ex); - if (_lastUpdate != 0) { - query = new QueryParameter().getFrom(queryGetAllColumns); - FotoSql.addWhereDateModifiedMinMax(query, _lastUpdate, 0); - // FotoSql.createCursorForQuery() } + return delCount; + } + + @Override + public ContentValues getDbContent(long id) { Cursor c = null; - ContentValues contentValues = new ContentValues(); try { - c = ContentProviderMediaImpl.createCursorForQuery(null, "execGetFotoPaths(pathFilter)", context, - query, null); - while (c.moveToNext()) { - getContentValues(c, contentValues); - save(db, c, contentValues, _lastUpdate); + c = this.createCursorForQuery(null, "getDbContent", + Impl.table, FotoSql.FILTER_COL_PK, new String[]{"" + id}, null, null, "*"); + if (c.moveToNext()) { + ContentValues values = new ContentValues(); + DatabaseUtils.cursorRowToContentValues(c, values); + return values; } } catch (Exception ex) { - // Log.e(Global.LOG_CONTEXT, "FotoSql.execGetFotoPaths() Cannot get path from: " + FotoSql.SQL_COL_PATH + " like '" + pathFilter +"'", ex); + Log.e(Global.LOG_CONTEXT, MODUL_NAME + + ".getDbContent(id=" + id + ") failed", ex); } finally { if (c != null) c.close(); } + return null; + } - if (Global.debugEnabled) { - // Log.d(Global.LOG_CONTEXT, "FotoSql.execGetFotoPaths() result count=" + result.size()); + public static class Impl { + /** + * SQL to create copy of contentprovider MediaStore.Images. + * copied from android-4.4 android database. Removed columns not used + */ + public static final String[] DDL = new String[]{ + "CREATE TABLE \"files\" (\n" + + "\t_id INTEGER PRIMARY KEY AUTOINCREMENT,\n" + + "\t_size INTEGER,\n" + + "\tdate_added INTEGER,\n" + + "\tdate_modified INTEGER,\n" + + "\tdatetaken INTEGER,\n" + + "\torientation INTEGER,\n" + + "\tduration INTEGER,\n" + + "\tbookmark INTEGER,\n" + + "\tmedia_type INTEGER,\n" + + "\twidth INTEGER,\n" + + "\theight INTEGER,\n" + + + "\t_data TEXT UNIQUE COLLATE NOCASE,\n" + + "\ttitle TEXT,\n" + + "\tdescription TEXT,\n" + + "\t_display_name TEXT,\n" + + "\tmime_type TEXT,\n" + + "\ttags TEXT,\n" + + + "\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)", + }; + + public static final String table = "files"; + // same colum order as in DDL + private static final String[] USED_MEDIA_COLUMNS = new String[]{ + // INTEGER 0 .. 10 + SQL_COL_PK, + SQL_COL_DATE_ADDED, + SQL_COL_LAST_MODIFIED, + SQL_COL_SIZE, + SQL_COL_DATE_TAKEN, + SQL_COL_ORIENTATION, + SQL_COL_EXT_XMP_LAST_MODIFIED_DATE, // duration + SQL_COL_EXT_RATING, // bookmark + SQL_COL_EXT_MEDIA_TYPE, + MediaStore.MediaColumns.WIDTH, + MediaStore.MediaColumns.HEIGHT, + + // TEXT 11 .. 16 + SQL_COL_PATH, // _data + SQL_COL_EXT_TITLE, + SQL_COL_EXT_DESCRIPTION, + SQL_COL__IMPL_DISPLAY_NAME, + MediaStore.MediaColumns.MIME_TYPE, + SQL_COL_EXT_TAGS, + + // DOUBLE 17..18 + SQL_COL_LAT, + SQL_COL_LON, + }; + + private static final int intMin = 0; + private static final int intMax = 10; + private static final int txtMin = 11; + private static final int txtMax = 16; + private static final int dblMin = 17; + private static final int dblMax = 18; + + private static final int colID = 0; + private static final int colDATE_ADDED = 1; + private static final int colLAST_MODIFIED = 2; + private static final String FILTER_EXPR_AFFECTED_FILES + = "(" + FotoSql.FILTER_EXPR_PRIVATE_PUBLIC + + " OR " + SQL_COL_PATH + " like '%" + AlbumFile.SUFFIX_VALBUM + "' " + + " OR " + SQL_COL_PATH + " like '%" + AlbumFile.SUFFIX_QUERY + "' " + + ")"; + private static final QueryParameter queryGetAllColumns = new QueryParameter() + .setID(QUERY_TYPE_UNDEFINED) + .addColumn(USED_MEDIA_COLUMNS) + .addFrom(SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME) + .addWhere(FILTER_EXPR_AFFECTED_FILES); + + private static boolean isLomg(int index) { + return index >= intMin && index <= intMax; } - return 0; - } - private static void save(SQLiteDatabase db, Cursor c, ContentValues contentValues, long lastUpdate) { - boolean isNew = (c.getLong(colDATE_ADDED) > lastUpdate); + // private Object get(Cursor cursor, columIndex) - if (isNew) { - db.insert(table, null, contentValues); - } else { - String[] params = new String[]{"" + c.getLong(colID)}; - contentValues.remove(SQL_COL_PK); - db.update(table, contentValues, FotoSql.FILTER_COL_PK, params); + private static boolean isString(int index) { + return index >= txtMin && index <= txtMax; + } + + private static boolean isDouble(int index) { + return index >= dblMin && index <= dblMax; + } + + private static ContentValues getContentValues(Cursor cursor, ContentValues destination) { + destination.clear(); + int colCount = cursor.getColumnCount(); + String columnName; + for (int i = 0; i < colCount; i++) { + columnName = cursor.getColumnName(i); + if (cursor.isNull(i)) { + destination.putNull(columnName); + } else if (isLomg(i)) { + destination.put(columnName, cursor.getLong(i)); + } else if (isString(i)) { + destination.put(columnName, cursor.getString(i)); + } else if (isDouble(i)) { + destination.put(columnName, cursor.getDouble(i)); + } + } + return destination; + } + + public static int updateMedaiCopy(Context context, SQLiteDatabase db, Date lastUpdate) { + int changeCount = 0; + + QueryParameter query = queryGetAllColumns; + long _lastUpdate = (lastUpdate != null) ? (lastUpdate.getTime() / 1000L) : 0L; + + if (_lastUpdate != 0) { + query = new QueryParameter().getFrom(queryGetAllColumns); + FotoSql.addWhereDateModifiedMinMax(query, _lastUpdate, 0); + // FotoSql.createCursorForQuery() + } + Cursor c = null; + ContentValues contentValues = new ContentValues(); + try { + c = ContentProviderMediaImpl.createCursorForQuery(null, "execGetFotoPaths(pathFilter)", context, + query, null, null); + while (c.moveToNext()) { + getContentValues(c, contentValues); + save(db, c, contentValues, _lastUpdate); + } + } catch (Exception ex) { + // Log.e(Global.LOG_CONTEXT, "FotoSql.execGetFotoPaths() Cannot get path from: " + FotoSql.SQL_COL_PATH + " like '" + pathFilter +"'", ex); + } finally { + if (c != null) c.close(); + } + + if (Global.debugEnabled) { + // Log.d(Global.LOG_CONTEXT, "FotoSql.execGetFotoPaths() result count=" + result.size()); + } + return 0; + } + + private static void save(SQLiteDatabase db, Cursor c, ContentValues contentValues, long lastUpdate) { + boolean isNew = (c.getLong(colDATE_ADDED) > lastUpdate); + + if (isNew) { + db.insert(table, null, contentValues); + } else { + String[] params = new String[]{"" + c.getLong(colID)}; + contentValues.remove(SQL_COL_PK); + db.update(table, contentValues, FotoSql.FILTER_COL_PK, params); + } } } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/SqlJobTaskBase.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/SqlJobTaskBase.java index 515915d5..fa741099 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/SqlJobTaskBase.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/SqlJobTaskBase.java @@ -67,7 +67,7 @@ protected SelectedItems doInBackground(QueryParameter... querys) { try { cursor = FotoSql.getMediaDBApi().createCursorForQuery( null, "SqlJobTask", - query, null); + query, null, null); int itemCount = cursor.getCount(); final int expectedCount = itemCount + itemCount; diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagSql.java b/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagSql.java index 169329f3..16af38d5 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagSql.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagSql.java @@ -380,7 +380,7 @@ public static int getTagRefCount(List tags) { if (addWhereAnyOfTags(query, tags) > 0) { Cursor c = null; try { - c = getMediaDBApi().createCursorForQuery(null, "getTagRefCount", query, VISIBILITY.PRIVATE_PUBLIC); + c = getMediaDBApi().createCursorForQuery(null, "getTagRefCount", query, VISIBILITY.PRIVATE_PUBLIC, null); if (c.moveToFirst()) { return c.getInt(0); } @@ -433,7 +433,7 @@ public static List loadTagWorflowItems(String selectedItemPks, L if (filterCount > 0) { try { - c = getMediaDBApi().createCursorForQuery(null, "loadTagWorflowItems", query, VISIBILITY.PRIVATE_PUBLIC); + c = getMediaDBApi().createCursorForQuery(null, "loadTagWorflowItems", query, VISIBILITY.PRIVATE_PUBLIC, null); if (c.moveToFirst()) { do { result.add(new TagWorflowItem(c.getLong(0), c.getString(1), TagConverter.fromString(c.getString(2)), diff --git a/app/src/main/java/de/k3b/android/util/PhotoPropertiesMediaFilesScanner.java b/app/src/main/java/de/k3b/android/util/PhotoPropertiesMediaFilesScanner.java index 5d9ac8de..47c89d18 100644 --- a/app/src/main/java/de/k3b/android/util/PhotoPropertiesMediaFilesScanner.java +++ b/app/src/main/java/de/k3b/android/util/PhotoPropertiesMediaFilesScanner.java @@ -300,7 +300,7 @@ private int renameInMediaDatabase(Context context, Map old2NewFi Cursor c = null; try { - c = FotoSql.getMediaDBApi().createCursorForQuery(null, "renameInMediaDatabase", query, VISIBILITY.PRIVATE_PUBLIC); + c = FotoSql.getMediaDBApi().createCursorForQuery(null, "renameInMediaDatabase", query, VISIBILITY.PRIVATE_PUBLIC, null); int pkColNo = c.getColumnIndex(FotoSql.SQL_COL_PK); int pathColNo = c.getColumnIndex(FotoSql.SQL_COL_PATH); while (c.moveToNext()) { diff --git a/fotolib2/src/main/java/de/k3b/database/QueryParameter.java b/fotolib2/src/main/java/de/k3b/database/QueryParameter.java index f422e026..3df9a9fd 100644 --- a/fotolib2/src/main/java/de/k3b/database/QueryParameter.java +++ b/fotolib2/src/main/java/de/k3b/database/QueryParameter.java @@ -277,12 +277,7 @@ public QueryParameter addOrderBy(String... orders) { } public String toOrderBy() { - StringBuilder result = new StringBuilder(); - if (!Helper.append(result, null, mOrderBy, ", ", "", "")) { - return null; - } - - return result.toString(); + return Helper.toCommaSeperatedFieldListOrNull(mOrderBy); } /************************** end properties *********************/ @@ -336,6 +331,18 @@ public StringBuilder toParsableWhere(StringBuilder result) { return result; } + public String toWhere() { + return Helper.toCommaSeperatedFieldListOrNull(mWhere); + } + + public String toHaving() { + return Helper.toCommaSeperatedFieldListOrNull(mHaving); + } + + public String toGroupBy() { + return Helper.toCommaSeperatedFieldListOrNull(mGroupBy); + } + private static final String PARSER_KEYWORDS = ";FROM;QUERY-TYPE-ID;SELECT;WHERE;WHERE-PARAMETERS;GROUP-BY;HAVING;HAVING-PARAMETERS;ORDER-BY;"; public static QueryParameter parse(String stringToBeParsed) { @@ -512,13 +519,13 @@ private static boolean append(StringBuilder result, String blockPrefix, List list) { + if (isNotEmpty(list)) { + StringBuilder result = new StringBuilder(); + append(result, null, list, ", ", null, null); + return result.toString(); + } + return null; + } + private static boolean isNotEmpty(List list) { return (list != null) && (list.size() > 0); } diff --git a/fotolib2/src/main/java/de/k3b/media/PhotoPropertiesWrapper.java b/fotolib2/src/main/java/de/k3b/media/PhotoPropertiesWrapper.java index 01b6e22d..6fa4a4cf 100644 --- a/fotolib2/src/main/java/de/k3b/media/PhotoPropertiesWrapper.java +++ b/fotolib2/src/main/java/de/k3b/media/PhotoPropertiesWrapper.java @@ -29,7 +29,6 @@ * * Created by k3b on 09.10.2016. */ - public class PhotoPropertiesWrapper implements IPhotoProperties { protected final IPhotoProperties readChild; protected final IPhotoProperties writeChild;