From 7a8365b973e2a950872cf8278c6a449765b3f7d7 Mon Sep 17 00:00:00 2001 From: k3b Date: Mon, 6 Mar 2017 09:35:09 +0100 Subject: [PATCH 01/37] prepare next fdroid release 0.6.0.170309 (25) Tag support --- app/build.gradle | 2 +- app/src/main/res/values/donottranslate_version.xml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e1917ece..2de02983 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,7 +13,7 @@ android { defaultConfig { // fdroid-release 'de.k3b.android.androFotoFinder' // main-develop-branch 'de.k3b.android.androFotoFinder.dev' - applicationId 'de.k3b.android.androFotoFinder.dev' + applicationId 'de.k3b.android.androFotoFinder' minSdkVersion 14 targetSdkVersion 21 diff --git a/app/src/main/res/values/donottranslate_version.xml b/app/src/main/res/values/donottranslate_version.xml index 110c050d..7009a5f4 100644 --- a/app/src/main/res/values/donottranslate_version.xml +++ b/app/src/main/res/values/donottranslate_version.xml @@ -24,9 +24,9 @@ this program. If not, see "(dev)" for development version @master --> - A Photo Manager (dev) - A Photo Viewer (dev) - A Photo Map (dev) - - " (dev)" + A Photo Manager + A Photo Viewer + A Photo Map + + "" From efedb5e78ecd39857f8aa6f5ea55c86b7d9fb1ac Mon Sep 17 00:00:00 2001 From: k3b Date: Mon, 1 May 2017 18:29:41 +0200 Subject: [PATCH 02/37] removed deprecated string resource --- app/src/main/res/values-ar/strings.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index d8a74b53..bbedbf84 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -27,7 +27,6 @@ %1$s علامات مرجعية تمت إزالتها %1$s لا يمكن إزالة العلامة المرجعية هل تريد إزالة هذه العلامة المرجعية؟ - %1$s لا يوجد علامات في تحميل العلامات المرجعية من… حفظ العلامة المرجعية كـ … إلغاء From 1afa335b4220a16ed6caefbf17161af4c28e1ee6 Mon Sep 17 00:00:00 2001 From: k3b Date: Mon, 1 May 2017 23:34:00 +0200 Subject: [PATCH 03/37] workaround "android stock icon as activity icon breaks aapt check in frdoid build" --- app/build.gradle | 5 +++-- app/src/main/AndroidManifest.xml | 2 +- app/src/main/res/drawable/ic_action_search.png | Bin 0 -> 1152 bytes 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 app/src/main/res/drawable/ic_action_search.png diff --git a/app/build.gradle b/app/build.gradle index aa799ec7..310abbba 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -38,9 +38,10 @@ android { // 0.6.0.170402 (27) language updates. (fdroid build failed) // 0.6.0.170404 (28) same as 0.6.0.170402. try to fix fdroid build // 0.6.0.170421 (29) bugfix Map + // 0.6.0.170502 (30) workaround "android stock icon as activity icon breaks aapt check in frdoid build" - versionCode = 29 - versionName = '0.6.0.170421' + versionCode = 30 + versionName = '0.6.0.170502' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_7 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 64756057..dbdd7ee9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -171,7 +171,7 @@ No+02}}-E$v#0vapRMt1bOs<(e91z^0$N)!mjH zMGyo*5ClOG1VIo4K@bE%5M*63r;EAWZi)OcreA0BDC8mN_dJf{ZvZfBaAo8<89Qn4 z76s%=$CnR4Vh2W7BRrMIQXZ3zAE!W^B#0{jG&g+u5N1qCg{i|3LBu^7Uxk>Rfe~M) zi#+xcL;(Q*jlZE$-&DCgo@0B`!fVv~Hxhyf8UTeHW&A}0gQ<~wc@;wB=S^v{{-BAw zZnFL@KkpnQ05bluCbM~a0&Ov$?72g*1z`7L|<6>yvBE5F0kcj6zS9{zxP2ELYJ-4Fn<2{gWerk>R+%b7A~{Bk7*?tZBqQ zE0Nc?@)`hzovJ+lV%zu)A*7T^`3K-3E2*4tdyT1g2&alal@qp?9-vDHom8droEfFL z&sxx6@eaVUD$h5OYIe{`^ZLRo0F?PmS{R@&FSu&FVCkC&RJ8?iCdE&t$%v*t0hnnD zF84*wJp>jFuX(Tl60QKsxv1i`i5CE<`qaf=E)qU_KvhbYGfTBu-zBpFUMYDbutm zR*sB6)p%geZIPA{0MvIE8ZVeT_+Gk2TBnGaOcr7XoiTK6YfGblqe(rl9M_2&13<2Q z(S)L79}wg@mMI*b!-~~jr^@{j^essz$gG(1GLdp$0dZ+u;Eqf<5-jS zF8U0Et!!p76l`mXx6YgE2<9qkwtGemiZwS+u(?B~4nhecYqNlhQp`p4-Y!da5UlPI zqeWZ48jR3#|F6&#b!AVd>3-tDKKIKVHKHOAe+<3JwKgm5D=U^D}tJIifqoIfq)VgGOP0Auzv!pl2vf?5jK7N5@=0;|W>ScE*MM1Wn!G>XE|{LS}SZ=r+& zpv!7#s}Mo~&{ftTgaV-83Lz8#C$13UKmc?}N3CW@!2sY2At(TrApGdE89lfxsOEVR z@5~9E*1-muh8l)20RXoU1OT{$(0QS6@Re{!2pQ5)ciEAV$DpAX^qwb_|BFi4fhPzm zzzP897lJalTEk>?g*5=^A(TbdgyQ`bR)HW0f*=TjAP9mW2!bF8f(EL;0t^5du&;5r SwHQ+X0000 Date: Sun, 13 Aug 2017 06:46:27 +0200 Subject: [PATCH 04/37] added crashlog info to .github/ISSUE_TEMPLATE.md --- .github/ISSUE_TEMPLATE.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index cb1e772b..c875898a 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -20,3 +20,14 @@ * [ ] [Folder-Picker](https://github.com/k3b/APhotoManager/wiki/Folder-Picker) * [ ] [Settings](https://github.com/k3b/APhotoManager/wiki/settings) * [ ] [Intent API](https://github.com/k3b/APhotoManager/wiki/intentapi) + +If you report an app crash: Can you add the crash logfile to this ticket? + +---- + +When APhotoManager crashes it tries to write a crash log file + +* [ExternalStorageDirectory]/copy/log/androFotofinder.logcat-2017....txt + * i.e. /storage/sdcard0/copy/log/androFotofinder.logcat-20170728-135704.txt + * crash data from 2017-07-28 13:57(for the used crash log path on your device see APhotoManager-Settings-Diagnostics- **Error Log Folder** ) + From 662778069a3ae2d2ef08af693da9a0450dbe51f1 Mon Sep 17 00:00:00 2001 From: k3b Date: Sun, 13 Aug 2017 06:49:24 +0200 Subject: [PATCH 05/37] Update ISSUE_TEMPLATE.md --- .github/ISSUE_TEMPLATE.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index c875898a..7d6c317c 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -10,7 +10,7 @@ * Android Version (i.e. 5.1): * ... -* [A Photo Manager Version](https://github.com/k3b/APhotoManager/releases) (i.e. 0.4.6.160304) : +* [A Photo Manager Version](https://github.com/k3b/APhotoManager/releases) (i.e. 0.6.1.170803) : * ... * Affected Module: * [ ] [Gallery-View](https://github.com/k3b/APhotoManager/wiki/Gallery-View) @@ -29,5 +29,6 @@ When APhotoManager crashes it tries to write a crash log file * [ExternalStorageDirectory]/copy/log/androFotofinder.logcat-2017....txt * i.e. /storage/sdcard0/copy/log/androFotofinder.logcat-20170728-135704.txt - * crash data from 2017-07-28 13:57(for the used crash log path on your device see APhotoManager-Settings-Diagnostics- **Error Log Folder** ) + * crash data from 2017-07-28 13:57 +(for the used crash log path on your device see APhotoManager-Settings-Diagnostics- **Error Log Folder** ) From e4824b9cc66c60cdfcf1664bee5c848bcc5e7308 Mon Sep 17 00:00:00 2001 From: k3b Date: Tue, 15 Aug 2017 16:53:03 +0200 Subject: [PATCH 06/37] update size info --- README.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 089738dc..0c25765a 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,20 @@ Enhanced Android [Gallery App](https://github.com/k3b/APhotoManager/wiki/Gallery-View) to manage local photos: [geotagging with map](https://github.com/k3b/APhotoManager/wiki/geographic-map), [tags (keywords)](https://github.com/k3b/APhotoManager/wiki/Tags), find, sort, view, copy, delete, set gps exif data, send, ... . -**DISCLAIMER com.PlayZone.quickimagegallery**: Sombody has created a clone of #APhotoManager app, changed some icons, added google-advertising-code (and may be other code???) and put it into google play store https://play.google.com/store/apps/details?id=com.PlayZone.quickimagegallery
I (k3b) have nothing to do with this clone and i cannot garantee that the clone has no unwanted buildin code. - -* sizeof com.PlayZone.quickimagegallery.apk version 0.5.5: 2.7 MB -* sizeof de.k3b.android.androFotoFinder.apk version 0.5.5: 1.7 MB -* sizeof de.k3b.android.androFotoFinder.apk version 0.6.0: 1.1 MB (better compression) -* sizeof de.k3b.android.androFotoFinder.apk version 0.6.1: 1.3 MB (more translations) +**DISCLAIMER Be cautious if you download "A Photo Manager" where the apk size is bigger than my builds**: + +It is likely that these versions contain unwanted code (maleware or adware). +I (k3b) have nothing to do with these clones and i cannot garantee that the clones has no unwanted buildin code. + +* sizeof de.k3b.android.androFotoFinder.apk version **0.5.5: 1.7 MB my official version** on [uptodown.com](http://a-photo-manager.en.uptodown.com/android) and [f-droid](https://f-droid.org/wiki/page/de.k3b.android.androFotoFinder) + * Same version 0.5.5: **3 MB** found on **[allfreeapk.com](https://m.allfreeapk.com/a-photo-manager,12132282/)** + * Same version 0.5.5: **2.7 MB** found at **[9apps.com](9apps.com/android-apps/Photo-Manager/)** +* . +* sizeof de.k3b.android.androFotoFinder.apk version **0.6.0: 1.1 MB** (better compression) my official version on [uptodown.com](http://a-photo-manager.en.uptodown.com/android) and [f-droid](https://f-droid.org/wiki/page/de.k3b.android.androFotoFinder) +* sizeof de.k3b.android.androFotoFinder.apk version **0.6.1: 1.2 MB** (more features, additional translations) my official version on [uptodown.com](http://a-photo-manager.en.uptodown.com/android) and [f-droid](https://f-droid.org/wiki/page/de.k3b.android.androFotoFinder) + * same version 0.6.1 but bigger apk file sites **1.6 MB**, **2.2MB** or **3.0MB** on [de.downloadatoz.com](https://de.downloadatoz.com/de-k3b-android-androfotofinder/de.k3b.android.androFotoFinder/), +[www.apkherunterladen.com](https://www.apkherunterladen.com/de-k3b-android-androfotofinder/de.k3b.android.androFotoFinder/downloading.html), +[www.androidapkdescargar.com](https://www.androidapkdescargar.com/a-photo-manager/de.k3b.android.androFotoFinder/) ## Features From d1f240d9843706aae21903b0180e80d1fb7205a6 Mon Sep 17 00:00:00 2001 From: k3b Date: Wed, 30 Aug 2017 14:40:47 +0200 Subject: [PATCH 07/37] added clone info co.mau --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0c25765a..6e46937b 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,8 @@ I (k3b) have nothing to do with these clones and i cannot garantee that the clon * same version 0.6.1 but bigger apk file sites **1.6 MB**, **2.2MB** or **3.0MB** on [de.downloadatoz.com](https://de.downloadatoz.com/de-k3b-android-androfotofinder/de.k3b.android.androFotoFinder/), [www.apkherunterladen.com](https://www.apkherunterladen.com/de-k3b-android-androfotofinder/de.k3b.android.androFotoFinder/downloading.html), [www.androidapkdescargar.com](https://www.androidapkdescargar.com/a-photo-manager/de.k3b.android.androFotoFinder/) - + * [co.mau photo manager on google play store](https://play.google.com/store/apps/details?id=co.mau&hl=en) and [co.mau photo manager on appbrain](https://www.appbrain.com/app/mau-photo-manager/co.mau) file size **2.81 MB** is APhotoManager with different icons, advertising (and may be other unwanted code) but without providing the sourcecode of the changes and therefore violating the gpl-license. + ## Features * Free, opensource, no adds, no user tracking, available on [f-droid](https://f-droid.org/) From 332b13ca47bc18dce9d508cfb6a622656f27c0d9 Mon Sep 17 00:00:00 2001 From: k3b Date: Mon, 13 Nov 2017 22:45:16 +0100 Subject: [PATCH 08/37] fixed merge error --- README.md | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 0c25765a..d417d5b3 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,21 @@ # ![](https://raw.githubusercontent.com/k3b/APhotoManager/master/app/src/main/res/drawable-hdpi/foto_gallery.png) "A Photo Manager" with "A Photo Map", AndroFotoFinder -Enhanced Android [Gallery App](https://github.com/k3b/APhotoManager/wiki/Gallery-View) to manage local photos: [geotagging with map](https://github.com/k3b/APhotoManager/wiki/geographic-map), [tags (keywords)](https://github.com/k3b/APhotoManager/wiki/Tags), -find, sort, view, copy, delete, set gps exif data, send, ... . +Enhanced Android [Gallery App](https://github.com/k3b/APhotoManager/wiki/Gallery-View) to manage local photos: +[geotagging with map](https://github.com/k3b/APhotoManager/wiki/geographic-map), +[tags (keywords)](https://github.com/k3b/APhotoManager/wiki/Tags), +find, sort, view, copy, delete, set gps exif data, set tags(Keywords), send, edit exif, ... . -**DISCLAIMER Be cautious if you download "A Photo Manager" where the apk size is bigger than my builds**: - -It is likely that these versions contain unwanted code (maleware or adware). -I (k3b) have nothing to do with these clones and i cannot garantee that the clones has no unwanted buildin code. - -* sizeof de.k3b.android.androFotoFinder.apk version **0.5.5: 1.7 MB my official version** on [uptodown.com](http://a-photo-manager.en.uptodown.com/android) and [f-droid](https://f-droid.org/wiki/page/de.k3b.android.androFotoFinder) - * Same version 0.5.5: **3 MB** found on **[allfreeapk.com](https://m.allfreeapk.com/a-photo-manager,12132282/)** - * Same version 0.5.5: **2.7 MB** found at **[9apps.com](9apps.com/android-apps/Photo-Manager/)** -* . -* sizeof de.k3b.android.androFotoFinder.apk version **0.6.0: 1.1 MB** (better compression) my official version on [uptodown.com](http://a-photo-manager.en.uptodown.com/android) and [f-droid](https://f-droid.org/wiki/page/de.k3b.android.androFotoFinder) -* sizeof de.k3b.android.androFotoFinder.apk version **0.6.1: 1.2 MB** (more features, additional translations) my official version on [uptodown.com](http://a-photo-manager.en.uptodown.com/android) and [f-droid](https://f-droid.org/wiki/page/de.k3b.android.androFotoFinder) - * same version 0.6.1 but bigger apk file sites **1.6 MB**, **2.2MB** or **3.0MB** on [de.downloadatoz.com](https://de.downloadatoz.com/de-k3b-android-androfotofinder/de.k3b.android.androFotoFinder/), -[www.apkherunterladen.com](https://www.apkherunterladen.com/de-k3b-android-androfotofinder/de.k3b.android.androFotoFinder/downloading.html), -[www.androidapkdescargar.com](https://www.androidapkdescargar.com/a-photo-manager/de.k3b.android.androFotoFinder/) +---
+[available on upToDown store](https://a-photo-manager.en.uptodown.com/android) [available on F-Droid app store](https://f-droid.org/app/de.k3b.android.androFotoFinder)
+[Downloads](https://github.com/k3b/APhotoManager/wiki/Download)
+**[DISCLAIMER Be cautious if you download "A Photo Manager" where the apk size is bigger than 1.5 Megabytes](https://github.com/k3b/APhotoManager/wiki/Download)**:
+---
## Features * Free, opensource, no adds, no user tracking, available on [f-droid](https://f-droid.org/) * Geotagging: Shows photos in a [geographic map](https://github.com/k3b/APhotoManager/wiki/geographic-map) from [openstreetmap](http://www.openstreetmap.org) -* Buildin file manager for photos: find, sort, view, copy, delete, Show in map, set gps exif data, send, ... . +* Buildin file manager for photos: find, sort, view, copy, delete, Show in map, set gps exif data, send, edit exif, ... . * Fast [find](https://github.com/k3b/APhotoManager/wiki/Filter-View) local photos by (sub-)folder, date, [geographic map](https://github.com/k3b/APhotoManager/wiki/geographic-map)or [tags (keywords)](https://github.com/k3b/APhotoManager/wiki/Tags). * [Translations](https://crowdin.com/project/AndroFotoFinder):  ar, deenitfrja, nl, pl, ro, ru trzh-CNzh-TW * [![Crowdin](https://d322cqt584bo4o.cloudfront.net/androFotoFinder/localized.svg)](https://crowdin.com/project/androFotoFinder)Help us to translate into other languages. @@ -37,7 +30,7 @@ I (k3b) have nothing to do with these clones and i cannot garantee that the clon * Current release * - * [available on F-Droid app store](https://f-droid.org/app/de.k3b.android.androFotoFinder) + * [available on upToDown store](https://a-photo-manager.en.uptodown.com/android) [available on F-Droid app store](https://f-droid.org/app/de.k3b.android.androFotoFinder) * [](https://github.com/k3b/APhotoManager/blob/master/LICENSE) or later. * Code Quality [![Codacy Badge](https://api.codacy.com/project/badge/Grade/df65509fc428454791603de5f3bb7707)](https://www.codacy.com/app/klaus3b-github/APhotoManager?utm_source=github.com&utm_medium=referral&utm_content=k3b/APhotoManager&utm_campaign=Badge_Grade) * Source code [branch FDroid link to buldserver](https://travis-ci.org/k3b/APhotoManager) @@ -49,6 +42,8 @@ I (k3b) have nothing to do with these clones and i cannot garantee that the clon ## Table of Contents * [Overview](https://github.com/k3b/APhotoManager/wiki/features) +* [Download](https://github.com/k3b/APhotoManager/wiki/Download) +* [Walk Through: Gallery, Filter, Map, Folder picker](https://github.com/k3b/APhotoManager/wiki/example-geosearch) * [Gallery-View](https://github.com/k3b/APhotoManager/wiki/Gallery-View) * [Geographic-Map](https://github.com/k3b/APhotoManager/wiki/geographic-map) * [Image-View](https://github.com/k3b/APhotoManager/wiki/Image-View) From ae98e6dba7aea78b33453a90ddb70ff8c9888e40 Mon Sep 17 00:00:00 2001 From: k3b Date: Mon, 13 Nov 2017 23:02:54 +0100 Subject: [PATCH 09/37] fixed readme download link image --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d417d5b3..280bdaaf 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Enhanced Android [Gallery App](https://github.com/k3b/APhotoManager/wiki/Gallery find, sort, view, copy, delete, set gps exif data, set tags(Keywords), send, edit exif, ... . ---
-[available on upToDown store](https://a-photo-manager.en.uptodown.com/android) [available on F-Droid app store](https://f-droid.org/app/de.k3b.android.androFotoFinder)
+[available on upToDown store](https://a-photo-manager.en.uptodown.com/android) [available on F-Droid app store](https://f-droid.org/app/de.k3b.android.androFotoFinder)
[Downloads](https://github.com/k3b/APhotoManager/wiki/Download)
**[DISCLAIMER Be cautious if you download "A Photo Manager" where the apk size is bigger than 1.5 Megabytes](https://github.com/k3b/APhotoManager/wiki/Download)**:
---
@@ -30,7 +30,7 @@ find, sort, view, copy, delete, set gps exif data, set tags(Keywords), send, edi * Current release * - * [available on upToDown store](https://a-photo-manager.en.uptodown.com/android) [available on F-Droid app store](https://f-droid.org/app/de.k3b.android.androFotoFinder) + * [available on upToDown store](https://a-photo-manager.en.uptodown.com/android) [available on F-Droid app store](https://f-droid.org/app/de.k3b.android.androFotoFinder) * [](https://github.com/k3b/APhotoManager/blob/master/LICENSE) or later. * Code Quality [![Codacy Badge](https://api.codacy.com/project/badge/Grade/df65509fc428454791603de5f3bb7707)](https://www.codacy.com/app/klaus3b-github/APhotoManager?utm_source=github.com&utm_medium=referral&utm_content=k3b/APhotoManager&utm_campaign=Badge_Grade) * Source code [branch FDroid link to buldserver](https://travis-ci.org/k3b/APhotoManager) From a05883001aee8bdff8d0af50aecdeb61bd958e88 Mon Sep 17 00:00:00 2001 From: k3b Date: Thu, 15 Mar 2018 18:46:01 +0100 Subject: [PATCH 10/37] Added "Highlights" to README.md --- README.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 280bdaaf..e7c1ed35 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,19 @@ # ![](https://raw.githubusercontent.com/k3b/APhotoManager/master/app/src/main/res/drawable-hdpi/foto_gallery.png) "A Photo Manager" with "A Photo Map", AndroFotoFinder -Enhanced Android [Gallery App](https://github.com/k3b/APhotoManager/wiki/Gallery-View) to manage local photos: -[geotagging with map](https://github.com/k3b/APhotoManager/wiki/geographic-map), -[tags (keywords)](https://github.com/k3b/APhotoManager/wiki/Tags), -find, sort, view, copy, delete, set gps exif data, set tags(Keywords), send, edit exif, ... . +Enhanced, privacy aware Android **[Gallery App](https://github.com/k3b/APhotoManager/wiki/Gallery-View)** to manage local photos: + +Highlights: +* [geotagging with map](https://github.com/k3b/APhotoManager/wiki/geographic-map), +* [Virtual Albums](https://github.com/k3b/APhotoManager/wiki/Bookmarks) filesystem-folder indepenant, +* management: on-move automatically [rename photo-files](https://github.com/k3b/AndroFotoFinder/wiki/AutoProcessing) and/or [add metadata](https://github.com/k3b/AndroFotoFinder/wiki/AutoProcessing), +* find, sort, view, copy, delete, set gps exif data, set [tags(keywords)](https://github.com/k3b/APhotoManager/wiki/Tags), send, ... . +* available in [many languages](https://github.com/k3b/APhotoManager/issues/21) + +Privacy: +* can [hide photos](https://github.com/k3b/APhotoManager/wiki/Exif-Edit#Visibility) from other gallery-apps/image-pickers. +* [Vault mode](https://github.com/k3b/APhotoManager/wiki/AppPinning): If enabled unwanted photos cannot be seen. +* photos are kept on local device. No upload to third party. +* no adds, no usertracking, free open source, available on f-droid ---
[available on upToDown store](https://a-photo-manager.en.uptodown.com/android) [available on F-Droid app store](https://f-droid.org/app/de.k3b.android.androFotoFinder)
From ef5a7028334fdd6cd5b8270e20883d622bd3c8af Mon Sep 17 00:00:00 2001 From: k3b Date: Mon, 19 Mar 2018 12:01:37 +0100 Subject: [PATCH 11/37] #116: fixed settings crash in release build --- app/src/main/res/xml/preferences.xml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 145fc9d1..ce0020fc 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -46,11 +46,11 @@ this program. If not, see - - @@ -59,11 +59,11 @@ this program. If not, see android:title="@string/settings_image_thumb_if_bigger_than_title" android:summary="@string/settings_image_thumb_if_bigger_than_summary"/> - - @@ -76,11 +76,11 @@ this program. If not, see android:title="@string/settings_maps_forge_enable_tile" android:summary="@string/settings_maps_forge_enable_summary" /> - - @@ -112,10 +112,10 @@ this program. If not, see android:title="@string/settings_multisel_clear_title" /> - - @@ -157,7 +157,7 @@ this program. If not, see - From a90044ae01ad4d019dd6c68d480815832ca4fde0 Mon Sep 17 00:00:00 2001 From: kodeblacc Date: Tue, 8 May 2018 21:36:50 +0100 Subject: [PATCH 12/37] Privacy.md Please review, make necessary changes and merge if it's okay by you. Thanks. --- privacy.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 privacy.md diff --git a/privacy.md b/privacy.md new file mode 100644 index 00000000..3741b1d7 --- /dev/null +++ b/privacy.md @@ -0,0 +1,30 @@ +Privacy Policy + APhotoManager app is an absolutely Open Source app. All services the software provides comes at no cost and is intended for use as is. + +This privacy policy document provides information regarding the developer's policies with the procurement, utilization, and disclosure of Personal Information should incase the services of the software is utilized by anyone. + +If you choose to utilize the services within the confines of APhotoManager, then you agree to the procurement and use of information in relation to this policy. The Personal Information that maybe collected is used for providing and improving the services of the software. These personal information garnered will not be used or disclosed with anyone except as described in this Privacy Policy. + +## Data collection and Usage + +For a better experience and optimum perfomance, while using APhotoManager, You may be required to provide permissions to your device storage which doesn't include any personally identifiable information. The requested permission is retained on your device and is not collected by me in any way + +The app does use third party services that may collect information used to identify you. + +## Log Data + +I want to inform you that whenever you use my Service, If settings ShowInMap/UseMapsforgeOfflineMap is disabled and some mapdata is shown, openstreetmap servers may contain logentries with your IP Adress, date/time and geographic coordinates you viewwed on the map. Each mapdata is only loaded once and then locally stored. + + +## Security + +I value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and I cannot guarantee its absolute security. + + +## Changes to This Privacy Policy + +This privacy policy may be updated from time to time. Thus, it is advisable to review this document periodically for any changes. . These changes are effective immediately after the privacy policy document is updated + +## Contact Us + +If you have any questions or suggestions about this Privacy Policy, do not hesitate to contact the developer via [github](https://github.com/k3b/APhotoManager) From 492d9d0480214048775b694a5c6addbcd0daabf6 Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Tue, 8 May 2018 23:30:33 +0200 Subject: [PATCH 13/37] Update privacy.md --- privacy.md | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/privacy.md b/privacy.md index 3741b1d7..21998d7c 100644 --- a/privacy.md +++ b/privacy.md @@ -1,25 +1,20 @@ -Privacy Policy - APhotoManager app is an absolutely Open Source app. All services the software provides comes at no cost and is intended for use as is. +# Privacy Policy -This privacy policy document provides information regarding the developer's policies with the procurement, utilization, and disclosure of Personal Information should incase the services of the software is utilized by anyone. - -If you choose to utilize the services within the confines of APhotoManager, then you agree to the procurement and use of information in relation to this policy. The Personal Information that maybe collected is used for providing and improving the services of the software. These personal information garnered will not be used or disclosed with anyone except as described in this Privacy Policy. +This app "A Photo Manager" is an absolutely Open Source app. All services the software provides comes at no cost and is intended for use as is. ## Data collection and Usage -For a better experience and optimum perfomance, while using APhotoManager, You may be required to provide permissions to your device storage which doesn't include any personally identifiable information. The requested permission is retained on your device and is not collected by me in any way - -The app does use third party services that may collect information used to identify you. - -## Log Data +You can use this app to add personal informations (geo data and comments) to your local photos which will also been transfered into android-s local media database for faster search and that will also become visible to other android apps. -I want to inform you that whenever you use my Service, If settings ShowInMap/UseMapsforgeOfflineMap is disabled and some mapdata is shown, openstreetmap servers may contain logentries with your IP Adress, date/time and geographic coordinates you viewwed on the map. Each mapdata is only loaded once and then locally stored. +This app will never transfer local photos or content of android-s local media database to any other computer/server. +Other Android Apps like "goolge photos" might transfer the photos with personal informations to other computer/server. -## Security +This app does not collect any other personal informoation. -I value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and I cannot guarantee its absolute security. +If you use the geographic map then map data may be fetched from OpenStreetMap servers so OpenStreetMap servers may be logging your ip address, date/time and the geographic area you are currently looking at. +Each mapdata piece is only loaded once and then locally stored. ## Changes to This Privacy Policy From 2953370a411896d7a1ccc64e5a777dcca56a5f6d Mon Sep 17 00:00:00 2001 From: kodeblacc Date: Wed, 9 May 2018 16:20:30 +0100 Subject: [PATCH 14/37] FAQs I've compiled a list of possible questions users of the app may want to ask. This FAQ document will serve as a self service tool for users. --- FAQs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 FAQs diff --git a/FAQs b/FAQs new file mode 100644 index 00000000..e80fb2f4 --- /dev/null +++ b/FAQs @@ -0,0 +1,52 @@ +Q. What is APhotoManager? + +A. APhotoManager is an opensource mobile gallery app that can manage your local photos. + +Q. What is geotagging? + +A. Geotagging is the act of assigning geographical location to a photographs or videos. + +Q. How do I add geotags to my photos using APhotoManager? + +A. Highlight the photos(s), click the option button and select "set geo" option, then you can decide to pick a location from map or from an existing photo. + +Q. What is exif data? + +A. Exif stands for Exchangeable image file format. It allows you to store certain information within your photos.This information is known as "metadata" and can include things like the date and time the shot was taken, camera settings like shutter speed and focal length, and copyright information. + +Q. How do I add, remove or edit exif data on my photo using APhotoManager? + +A. Highlight the photo(s), click the option button and select "set geo" option, then you can decide to pick a location from map or from an existing photo. + + +Q. Can I select a folder to view pictures? + +A. Yes, by clicking the folder icon on the top, you are shown all the folders that contain pictures on your deivce, selecting any folder and clicking ok allows you to view only pictures contained in that folder. + +Q. How do I find pictures on APhotoManager? + +A. Specific photos can be found on APhotoManager using the filter option by providing some information of the photo which may include the filename, path, tags etc. Providing any of those information will open up results relating to information provided. + +Q. Can I sort my photos on APhotoManager? + +A. Yes the sorting feature is available on APhotoManager. Photos can be sorted by name, date, place, rating, last modified, width, size and file path length, all in ascending order. + +Q. Can I share photos from APhotoManager? + +A. You can share photos from APhotoManager to any other location that is supported for such operation. To share photo(s), from APhotomanager you can highlight the picture(s) and the share button will be visible at the top, clicking it will pop-up several locations where the picture(s) can be shared to. The share button is automatically visible when a picture is viewed. + + +Q. Which photo management options are available on APhotoManager? + +A. File management options like, rename, copy and move are available on APhotoManager. + +Q. Are my photos uploaded to third party apps? + +A. No, photos are stored locally on the device, there's no upload to third party applications. + +Q. Does APhotoManager contain ads? + +A. APhotoManager does not contain ads, no usertracking, it is absolutely free and open source. + + +Thank you for using APhotoManager, if you like the app and wish to make contributions to the project, contact the developer via [github](https://github.com/k3b/APhotoManager) From 90f6408899c86529d910543dc15317438c5270ba Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Sat, 14 Jul 2018 12:22:10 +0200 Subject: [PATCH 15/37] Delete ISSUE_TEMPLATE.md --- .github/ISSUE_TEMPLATE.md | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 7d6c317c..00000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,34 +0,0 @@ -### Expected behavior - -... - -### Actual behavior - -... - -### Environment - -* Android Version (i.e. 5.1): - * ... -* [A Photo Manager Version](https://github.com/k3b/APhotoManager/releases) (i.e. 0.6.1.170803) : - * ... -* Affected Module: - * [ ] [Gallery-View](https://github.com/k3b/APhotoManager/wiki/Gallery-View) - * [ ] [Geographic-Map](https://github.com/k3b/APhotoManager/wiki/geographic-map) - * [ ] [Image-View](https://github.com/k3b/APhotoManager/wiki/Image-View) - * [ ] [Filtering](https://github.com/k3b/APhotoManager/wiki/Filter-View) - * [ ] [Folder-Picker](https://github.com/k3b/APhotoManager/wiki/Folder-Picker) - * [ ] [Settings](https://github.com/k3b/APhotoManager/wiki/settings) - * [ ] [Intent API](https://github.com/k3b/APhotoManager/wiki/intentapi) - -If you report an app crash: Can you add the crash logfile to this ticket? - ----- - -When APhotoManager crashes it tries to write a crash log file - -* [ExternalStorageDirectory]/copy/log/androFotofinder.logcat-2017....txt - * i.e. /storage/sdcard0/copy/log/androFotofinder.logcat-20170728-135704.txt - * crash data from 2017-07-28 13:57 - -(for the used crash log path on your device see APhotoManager-Settings-Diagnostics- **Error Log Folder** ) From a46b0bb42e4d9560cc5234b905de6bff40811ed9 Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Sat, 14 Jul 2018 12:54:12 +0200 Subject: [PATCH 16/37] created issue templates --- .github/ISSUE_TEMPLATE/feature_request.md | 26 +++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..26971205 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,26 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. + +* Affected Module: + * [ ] [Gallery-View](https://github.com/k3b/APhotoManager/wiki/Gallery-View) + * [ ] [Geographic-Map](https://github.com/k3b/APhotoManager/wiki/geographic-map) + * [ ] [Image-View](https://github.com/k3b/APhotoManager/wiki/Image-View) + * [ ] [Filtering](https://github.com/k3b/APhotoManager/wiki/Filter-View) + * [ ] [Folder-Picker](https://github.com/k3b/APhotoManager/wiki/Folder-Picker) + * [ ] [Settings](https://github.com/k3b/APhotoManager/wiki/settings) + * [ ] [Intent API](https://github.com/k3b/APhotoManager/wiki/intentapi) From 9a1414b411baa9bf847c425d3ba11bc9defc7cb3 Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Sat, 14 Jul 2018 12:55:53 +0200 Subject: [PATCH 17/37] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 36 ++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..f81639ff --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,36 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Smartphone (please complete the following information):** + - Android version [e.g. Android-7.1] : + - [A Photo Manager Version](https://github.com/k3b/APhotoManager/releases) (i.e. 0.4.6.160304) : + +**Additional context** +Add any other context about the problem here. + +**Crash Report** +If you report an app crash: Can you add the crash logfile to this ticket? + +When APhotoManager crashes it tries to write a crash log file in the [**Error Log Folder**](https://github.com/k3b/APhotoManager/wiki/settings#logfolder). + +* [ExternalStorageDirectory]/copy/log/androFotofinder.logcat-2017....txt + * i.e. /storage/sdcard0/copy/log/androFotofinder.logcat-20170728-135704.txt + * crash data from 2017-07-28 13:57 + +For more details on Error Loging see [diagnostic settings](https://github.com/k3b/APhotoManager/wiki/settings#Diagnostics). From b15957ca34da1a5121d458ee6e3342cdd42be01b Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Wed, 12 Jun 2019 13:06:19 +0200 Subject: [PATCH 18/37] replaced http: with https: --- fotolib2/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fotolib2/build.gradle b/fotolib2/build.gradle index aedfc4de..b4dcead0 100644 --- a/fotolib2/build.gradle +++ b/fotolib2/build.gradle @@ -20,7 +20,7 @@ dependencies { // compile 'com.adobe.xmp:xmpcore:6.1.10' // update for drewnoakes:metadata-extractor requires java-8 compiler does not run on my android-4.4. compile 'com.adobe.xmp:xmpcore:5.1.2' // current version for drewnoakes:metadata-extractor - // https://github.com/drewnoakes/metadata-extractor/wiki/GettingStarted licence: http://www.apache.org/licenses/LICENSE-2.0 + // https://github.com/drewnoakes/metadata-extractor/wiki/GettingStarted licence: https://www.apache.org/licenses/LICENSE-2.0 // 2.10.1 includes more recent xmpcore that generates java-8 bytecode not supported by android yet compile ('com.drewnoakes:metadata-extractor:2.10.1') { transitive = false } // 2.8.1 From e4583a68c59279ad2ab3d9974a1cb34df263b546 Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Wed, 12 Jun 2019 13:08:59 +0200 Subject: [PATCH 19/37] replaced http: with https: --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 91b27325..0ae1e345 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -86,7 +86,7 @@ android { } lintOptions { - // http://stackoverflow.com/questions/31350350/generating-signed-apk-error7-missingtranslation-in-build-generated-res-gen + // https://stackoverflow.com/questions/31350350/generating-signed-apk-error7-missingtranslation-in-build-generated-res-gen // MissingTranslation : not all crowdwin translations are complete so ignore them // ValidFragment : local (dialog-)fragment class for customized direcotry picker is fragile but on rotation code makes shure that dialog is closed. // disable 'MissingTranslation','ValidFragment', 'ContentDescription', 'AndroidLintPluralsCandidate', 'AndroidLintRtlHardcoded' From 20ae06c3811bbb7a0fe0e49aa487631600127894 Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Wed, 12 Jun 2019 13:10:00 +0200 Subject: [PATCH 20/37] replaced http: with https: --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 0ae1e345..21ac88ab 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -137,7 +137,7 @@ dependencies { // implementation 'com.android.support:support-v4:21.0.2' // implementation 'com.android.support:appcompat-v7:21.0.2' // exif support - // https://github.com/drewnoakes/metadata-extractor/wiki/GettingStarted licence: http://www.apache.org/licenses/LICENSE-2.0 + // https://github.com/drewnoakes/metadata-extractor/wiki/GettingStarted licence: https://www.apache.org/licenses/LICENSE-2.0 // 2.10.1 includes more recent xmpcore that generates java-8 bytecode not supported by android yet implementation ('com.drewnoakes:metadata-extractor:2.10.1') { transitive = false } // 2.8.1 From e81f98bbe8573e08e5022e428ac5bb5b0b64a920 Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Thu, 20 Jun 2019 08:29:34 +0200 Subject: [PATCH 21/37] fixed travis build error error message: The command "yes | sdkmanager "platforms;android-28"" failed and exited with 127 during . commented out statement --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f9253d02..2bda35ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,7 @@ before_install: # instead of # - chmod +x gradlew # https://stackoverflow.com/questions/52274229/travis-ci-android-28-licenses-have-not-been-accepted -- yes | sdkmanager "platforms;android-28" +# - yes | sdkmanager "platforms;android-28" script: - jdk_switcher use oraclejdk8 From 4ecd35744795e3c264632494696c0bc59bddf46e Mon Sep 17 00:00:00 2001 From: eve Date: Thu, 1 Aug 2019 11:16:58 +0200 Subject: [PATCH 22/37] #143: hotfix to reallow openstreetmap online map download --- app/build.gradle | 5 +++-- .../de/k3b/android/androFotoFinder/AndroFotoFinderApp.java | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 21ac88ab..833f8bc9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -52,9 +52,10 @@ android { // 0.7.1.180830 (39) Bugfix for 38; translation uk // 0.7.2.181027 (40) Bugfixes: image orientation, media scanner // 0.7.3.190424 (41) mapsforge-v5, filter date-last-modified, Fix getContent, exif-TAG_DATETIME, Translation ja, nl, ru, pt-br, uk + // 0.7.4.190801 (42) hotfix to reallow openstreetmap online map download - versionCode = 41 - versionName = '0.7.3.190424' + versionCode = 42 + versionName = '0.7.4.190801' // all supported locales // resConfigs "ar","de","es","fr","hi","in","it","ja","nl","pl","ro","ru","tr","uk","zz","pt-rBR","zh-rCN","zh-rTW" diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java index 9cb55492..c6241c9a 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 by k3b. + * Copyright (c) 2015-2019 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -26,6 +26,7 @@ import android.widget.Toast; import org.osmdroid.api.IMapView; +import org.osmdroid.config.Configuration; import java.io.File; import java.util.ArrayList; @@ -134,6 +135,8 @@ private File getOutpuFile() { //https://github.com/osmdroid/osmdroid/issues/366 //super important. Many tile servers, including open street maps, will BAN applications by user //??? OpenStreetMapTileProviderConstants.setUserAgentValue(getAppId() + " https://github.com/k3b/APhotoManager"); // BuildConfig.APPLICATION_ID); + // https://github.com/k3b/APhotoManager/issues/143 + Configuration.getInstance().setUserAgentValue(getAppId() + " https://github.com/k3b/APhotoManager"); // BuildConfig.APPLICATION_ID); // #60: configure some of the mapsforge settings first MapsForgeSupport.createInstance(this); From 750778e92570a85b51ad532d9f2002ef05c7b0a4 Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Fri, 2 Aug 2019 06:00:27 +0200 Subject: [PATCH 23/37] #143: hotfix to reallow openstreetmap online map download --- .github/feature_request.md | 20 +++++++++++++++++++ bug_report.md | 0 .../metadata/android/en-US/changelogs/42.txt | 3 +++ 3 files changed, 23 insertions(+) create mode 100644 .github/feature_request.md create mode 100644 bug_report.md create mode 100644 fastlane/metadata/android/en-US/changelogs/42.txt diff --git a/.github/feature_request.md b/.github/feature_request.md new file mode 100644 index 00000000..6e7c2330 --- /dev/null +++ b/.github/feature_request.md @@ -0,0 +1,20 @@ +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. + +* Affected Module: + * [ ] [Gallery-View](https://github.com/k3b/APhotoManager/wiki/Gallery-View) + * [ ] [Geographic-Map](https://github.com/k3b/APhotoManager/wiki/geographic-map) + * [ ] [Image-View](https://github.com/k3b/APhotoManager/wiki/Image-View) + * [ ] [Filtering](https://github.com/k3b/APhotoManager/wiki/Filter-View) + * [ ] [Folder-Picker](https://github.com/k3b/APhotoManager/wiki/Folder-Picker) + * [ ] [Settings](https://github.com/k3b/APhotoManager/wiki/settings) + * [ ] [Intent API](https://github.com/k3b/APhotoManager/wiki/intentapi) diff --git a/bug_report.md b/bug_report.md new file mode 100644 index 00000000..e69de29b diff --git a/fastlane/metadata/android/en-US/changelogs/42.txt b/fastlane/metadata/android/en-US/changelogs/42.txt new file mode 100644 index 00000000..b8c2cde9 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/42.txt @@ -0,0 +1,3 @@ +Changes from 0.7.3 to 0.7.4 + +* #143 hotfix to reallow openstreetmap online map download From 26355d1123f669a387070ec900441f7fa5861f6f Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Thu, 21 Nov 2019 10:57:23 +0100 Subject: [PATCH 24/37] #155: android10 ("A10") compatibility test branch --- app/build.gradle | 5 +++-- app/src/main/res/values/donottranslate_version.xml | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b9141886..e3031efb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,8 +14,9 @@ android { defaultConfig { // fdroid-release 'de.k3b.android.androFotoFinder' // main-develop-branch 'de.k3b.android.androFotoFinder.dev' - applicationId 'de.k3b.android.androFotoFinder' - + // applicationId 'de.k3b.android.androFotoFinder' + applicationId 'de.k3b.android.androFotoFinder.a10' + minSdkVersion 14 // Android 4.0 Ice Cream Sandwich (API 14); Android 4.4 KitKat (API 19); Android 5.0 Lollipop (API 21); // Android 6.0 Marshmallow (API 23); Android 7.0 Nougat (API 24) targetSdkVersion 21 diff --git a/app/src/main/res/values/donottranslate_version.xml b/app/src/main/res/values/donottranslate_version.xml index 7009a5f4..1537bc21 100644 --- a/app/src/main/res/values/donottranslate_version.xml +++ b/app/src/main/res/values/donottranslate_version.xml @@ -24,9 +24,9 @@ this program. If not, see "(dev)" for development version @master --> - A Photo Manager - A Photo Viewer - A Photo Map + A Photo Manager(A10) + A Photo Viewer(A10) + A Photo Map(A10) - "" + "A10" From c889d58f7bb5f4b4e20f608f2c35b44c188700b6 Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Thu, 21 Nov 2019 10:58:31 +0100 Subject: [PATCH 25/37] #155: android10. remove sqLite function max() --- .../java/de/k3b/android/androFotoFinder/queries/FotoSql.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 5d267ed7..f5e54f5a 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 @@ -135,7 +135,8 @@ public class FotoSql extends FotoSqlBase { // only works with api >= 16 public static final String SQL_COL_MAX_WITH = - (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) + // #155: android10 incompatibility: check if the sqLite-max() function is the problem + (false && (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)) ? "max(" + MediaStore.Images.Media.WIDTH + "," + MediaStore.Images.Media.HEIGHT +")" : "1024"; From 60afb0f0c8204d702921ce0ae8d4e2dedec8f65a Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Wed, 27 Nov 2019 21:52:57 +0100 Subject: [PATCH 26/37] #155: prepare to fix android10 incompatibility: MediaImageDbReplacement(own db-table copy of media db); refactored all context.contentresolver.query/insert/update/delete into seperate ContentProviderMediaExecuter --- .../backup/Backup2ZipService.java | 10 +- .../directory/DirectoryLoaderTask.java | 7 +- .../directory/DirectoryPickerFragment.java | 5 +- .../gallery/cursor/GalleryCursorFragment.java | 3 +- .../ImageDetailMetaDialogBuilder.java | 9 +- .../locationmap/LocationMapFragment.java | 2 +- .../locationmap/MarkerLoaderTask.java | 11 +- .../queries/AndroidAlbumUtils.java | 2 +- .../queries/ContentProviderMediaExecuter.java | 277 ++++++++++++++++++ .../queries/DatabaseHelper.java | 33 +-- .../androFotoFinder/queries/FotoSql.java | 259 ++-------------- .../androFotoFinder/queries/FotoThumbSql.java | 4 +- .../queries/MediaImageDbReplacement.java | 219 ++++++++++++++ .../queries/SqlJobTaskBase.java | 8 +- .../android/androFotoFinder/tagDB/TagSql.java | 33 +-- .../k3b/android/util/AndroidFileCommands.java | 3 +- .../PhotoPropertiesMediaFilesScanner.java | 23 +- 17 files changed, 589 insertions(+), 319 deletions(-) create mode 100644 app/src/main/java/de/k3b/android/androFotoFinder/queries/ContentProviderMediaExecuter.java create mode 100644 app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaImageDbReplacement.java 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 2c9e1a1e..7de70da1 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 @@ -19,10 +19,8 @@ package de.k3b.android.androFotoFinder.backup; -import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; -import android.net.Uri; import android.support.annotation.NonNull; import android.util.Log; @@ -33,6 +31,7 @@ import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.R; import de.k3b.android.androFotoFinder.media.PhotoPropertiesMediaDBCursor; +import de.k3b.android.androFotoFinder.queries.ContentProviderMediaExecuter; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.androFotoFinder.tagDB.TagSql; import de.k3b.database.QueryParameter; @@ -208,13 +207,12 @@ public static QueryParameter getEffectiveQueryParameter(@NonNull IZipConfig zipC /** calls consumers for each found query-result-item */ private void execQuery(QueryParameter query, IItemSaver... consumers) { - ContentResolver contentResolver = context.getContentResolver(); - Cursor cursor = null; try { this.onProgress(0,0, "Calculate"); - cursor = contentResolver.query(Uri.parse(query.toFrom()), query.toColumns(), - query.toAndroidWhere(), query.toAndroidParameters(), query.toOrderBy()); + cursor = ContentProviderMediaExecuter.createCursorForQuery( + null, "ZipExecute", context, + query, 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 b5ddfbd7..67df3563 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 @@ -22,13 +22,13 @@ import android.app.Activity; import android.content.Context; import android.database.Cursor; -import android.net.Uri; import android.os.AsyncTask; import android.util.Log; import java.util.List; import de.k3b.android.androFotoFinder.Global; +import de.k3b.android.androFotoFinder.queries.ContentProviderMediaExecuter; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.database.QueryParameter; import de.k3b.io.Directory; @@ -102,8 +102,9 @@ protected IDirectory doInBackground(QueryParameter... queryParameter) { } try { - cursor = context.getContentResolver().query(Uri.parse(queryParameters.toFrom()), queryParameters.toColumns(), - queryParameters.toAndroidWhere(), queryParameters.toAndroidParameters(), queryParameters.toOrderBy()); + cursor = ContentProviderMediaExecuter.createCursorForQuery( + null, "ZipExecute", context, + queryParameters, null); int itemCount = cursor.getCount(); final int expectedCount = itemCount + itemCount; diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryPickerFragment.java b/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryPickerFragment.java index 9265901c..1806edc3 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryPickerFragment.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryPickerFragment.java @@ -62,6 +62,7 @@ import de.k3b.android.androFotoFinder.ThumbNailUtils; import de.k3b.android.androFotoFinder.backup.BackupActivity; import de.k3b.android.androFotoFinder.imagedetail.ImageDetailMetaDialogBuilder; +import de.k3b.android.androFotoFinder.queries.ContentProviderMediaExecuter; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.androFotoFinder.queries.FotoThumbSql; import de.k3b.android.androFotoFinder.queries.FotoViewerParameter; @@ -692,9 +693,9 @@ private boolean fixLinks(IDirectory linkDir) { if (cann == null) { // rename linkFile to canonicalFile updateValues.put(FotoSql.SQL_COL_PATH, canonicalPath + lin.substring(linkPath.length())); - FotoSql.execUpdate("fixLinks", context, linkIds[i].intValue() ,updateValues); + ContentProviderMediaExecuter.execUpdate("fixLinks", context, linkIds[i].intValue(), updateValues); } else { - FotoSql.deleteMedia("DirectoryPickerFragment.fixLinks", context, FotoSql.FILTER_COL_PK, new String[] {linkIds[i].toString()}, true); + ContentProviderMediaExecuter.deleteMedia("DirectoryPickerFragment.fixLinks", context, FotoSql.FILTER_COL_PK, new String[]{linkIds[i].toString()}, true); } } PhotoPropertiesMediaFilesScanner.notifyChanges(context, "Fixed link/canonical duplicates"); 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 24a0f11b..224b8732 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.ContentProviderMediaExecuter; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.androFotoFinder.queries.FotoViewerParameter; import de.k3b.android.androFotoFinder.queries.Queryable; @@ -1416,7 +1417,7 @@ private void onDuplicatesFound(SelectedItems selectedItems, StringBuffer debugMe String sqlWhere = query.toAndroidWhere(); // + " OR " + FotoSql.SQL_COL_PATH + " is null"; try { - delCount = FotoSql.deleteMedia(mDebugPrefix + "onDuplicatesFound", activity, sqlWhere, null, true); + delCount = ContentProviderMediaExecuter.deleteMedia(mDebugPrefix + "onDuplicatesFound", activity, sqlWhere, null, true); } catch (Exception ex) { Log.w(Global.LOG_CONTEXT, "deleteMedia via update failed for 'where " + sqlWhere + "'."); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailMetaDialogBuilder.java b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailMetaDialogBuilder.java index c6e1c239..880d922d 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailMetaDialogBuilder.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailMetaDialogBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2018 by k3b. + * Copyright (c) 2015-2019 by k3b. * * This file is part of AndroFotoFinder. * @@ -33,13 +33,14 @@ import java.util.Date; import java.util.List; +import de.k3b.android.androFotoFinder.queries.ContentProviderMediaExecuter; import de.k3b.android.androFotoFinder.tagDB.TagSql; import de.k3b.android.widget.ActivityWithCallContext; +import de.k3b.database.QueryParameter; import de.k3b.io.DateUtil; +import de.k3b.io.FileCommands; import de.k3b.media.ExifInterfaceEx; -import de.k3b.database.QueryParameter; import de.k3b.media.PhotoPropertiesImageReader; -import de.k3b.io.FileCommands; import de.k3b.media.XmpSegment; /** @@ -137,7 +138,7 @@ private static void appendExifInfo(StringBuilder result, Activity context, Strin if (currentImageId != 0) { - ContentValues dbContent = TagSql.getDbContent(context, currentImageId); + ContentValues dbContent = ContentProviderMediaExecuter.getDbContent(context, currentImageId); if (dbContent != null) { result.append(NL).append(line).append(NL); result.append(NL).append(TagSql.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE).append(NL).append(NL); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/LocationMapFragment.java b/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/LocationMapFragment.java index 75d9954d..7abacfe5 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/LocationMapFragment.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/LocationMapFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2018 by k3b. + * Copyright (c) 2015-2019 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * 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 b450f46b..8dae8276 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 by k3b. + * Copyright (c) 2015-2019 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -22,7 +22,6 @@ import android.app.Activity; import android.database.Cursor; import android.graphics.drawable.BitmapDrawable; -import android.net.Uri; import android.os.AsyncTask; import android.util.Log; @@ -34,9 +33,10 @@ import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.R; +import de.k3b.android.androFotoFinder.queries.ContentProviderMediaExecuter; import de.k3b.android.androFotoFinder.queries.FotoSql; -import de.k3b.android.osmdroid.IconFactory; import de.k3b.android.osmdroid.ClickableIconOverlay; +import de.k3b.android.osmdroid.IconFactory; import de.k3b.android.util.ResourceUtils; import de.k3b.database.QueryParameter; @@ -96,8 +96,9 @@ protected OverlayManager doInBackground(QueryParameter... queryParameter) { Cursor cursor = null; try { - cursor = mContext.getContentResolver().query(Uri.parse(queryParameters.toFrom()), queryParameters.toColumns(), - queryParameters.toAndroidWhere(), queryParameters.toAndroidParameters(), queryParameters.toOrderBy()); + cursor = ContentProviderMediaExecuter.createCursorForQuery( + null, "MakerLoader", mContext, + queryParameters, null); int itemCount = cursor.getCount(); final int expectedCount = itemCount + itemCount; diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/AndroidAlbumUtils.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/AndroidAlbumUtils.java index 5a4e38f6..9bceb428 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/AndroidAlbumUtils.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/AndroidAlbumUtils.java @@ -368,7 +368,7 @@ public static void insertToMediaDB(String dbgContext, @NonNull Context context, ContentValues values = new ContentValues(); String newAbsolutePath = PhotoPropertiesMediaFilesScanner.setFileFields(values, fileToBeScannedAndInserted); values.put(FotoSql.SQL_COL_EXT_MEDIA_TYPE, FotoSql.MEDIA_TYPE_ALBUM_FILE); - FotoSql.insertOrUpdateMediaDatabase(dbgContext, context, newAbsolutePath, values, null, 1l); + ContentProviderMediaExecuter.insertOrUpdateMediaDatabase(dbgContext, context, newAbsolutePath, values, null, 1l); } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/ContentProviderMediaExecuter.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/ContentProviderMediaExecuter.java new file mode 100644 index 00000000..7eee65be --- /dev/null +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/ContentProviderMediaExecuter.java @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2015-2019 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see + */ +package de.k3b.android.androFotoFinder.queries; + +import android.content.ContentProviderOperation; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.net.Uri; +import android.util.Log; + +import java.util.ArrayList; + +import de.k3b.LibGlobal; +import de.k3b.android.androFotoFinder.Global; +import de.k3b.database.QueryParameter; +import de.k3b.io.StringUtils; +import de.k3b.io.VISIBILITY; + +public class ContentProviderMediaExecuter { + public static Cursor createCursorForQuery( + StringBuilder out_debugMessage, String dbgContext, final Context context, + QueryParameter parameters, VISIBILITY visibility) { + if (visibility != null) FotoSql.setWhereVisibility(parameters, visibility); + return createCursorForQuery(out_debugMessage, dbgContext, context, parameters.toFrom(), + parameters.toAndroidWhere(), + parameters.toAndroidParameters(), parameters.toOrderBy(), + 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) { + ContentResolver resolver = context.getContentResolver(); + Cursor query = null; + + Exception excpetion = null; + try { + 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", + QueryParameter.toString(sqlSelectColums, null, from, sqlWhereStatement, + sqlWhereParameters, sqlSortOrder, query.getCount())); + if (out_debugMessage == null) { + Log.i(Global.LOG_CONTEXT, message.toString(), excpetion); + } // else logging is done by caller + } + } + + 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}); + } + + /** + * every database update should go through this. adds logging if enabled + */ + public static int exexUpdateImpl(String dbgContext, Context context, ContentValues values, String sqlWhere, String[] selectionArgs) { + int result = -1; + Exception excpetion = null; + try { + result = context.getContentResolver().update(FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE, + values, sqlWhere, + selectionArgs); + } catch (Exception ex) { + excpetion = ex; + } finally { + if ((excpetion != null) || ((dbgContext != null) && (Global.debugEnabledSql || LibGlobal.debugEnabledJpg))) { + Log.i(Global.LOG_CONTEXT, dbgContext + ":FotoSql.exexUpdate " + excpetion + "\n" + + QueryParameter.toString(null, values.toString(), FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, + sqlWhere, selectionArgs, null, result), excpetion); + } + } + return result; + } + + /** + * return id of inserted item + */ + public static Long insertOrUpdateMediaDatabase(String dbgContext, Context context, + String dbUpdateFilterJpgFullPathName, + ContentValues values, VISIBILITY visibility, + Long updateSuccessValue) { + Long result = updateSuccessValue; + + int modifyCount = execUpdate(dbgContext, context, dbUpdateFilterJpgFullPathName, + values, visibility); + + if (modifyCount == 0) { + // update failed (probably becauce oldFullPathName not found. try insert it. + FotoSql.addDateAdded(values); + + Uri uriWithId = execInsert(dbgContext, context, values); + result = FotoSql.getId(uriWithId); + } + return result; + } + + /** + * every database insert should go through this. adds logging if enabled + */ + public static Uri execInsert(String dbgContext, Context context, ContentValues values) { + Uri providerUri = (null != values.get(FotoSql.SQL_COL_EXT_MEDIA_TYPE)) ? FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE : FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI; + + Uri result = null; + Exception excpetion = null; + try { + // on my android-4.4 insert with media_type=1001 (private) does insert with media_type=1 (image) + result = context.getContentResolver().insert(providerUri, values); + } catch (Exception ex) { + excpetion = ex; + } finally { + if ((excpetion != null) || Global.debugEnabledSql || LibGlobal.debugEnabledJpg) { + Log.i(Global.LOG_CONTEXT, dbgContext + ":FotoSql.execInsert " + excpetion + " " + + values.toString() + " => " + result + " " + excpetion, excpetion); + } + } + return result; + } + + /** + * Deletes media items specified by where with the option to prevent cascade delete of the image. + */ + public static int deleteMedia(String dbgContext, Context context, 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: FotoSql.deleteMedia: ", + context, values, lastUsedWhereClause, lastSelectionArgs); + + lastUsedWhereClause = FotoSql.SQL_COL_PATH + " is null"; + lastSelectionArgs = null; + 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" + + QueryParameter.toString(null, null, FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, + lastUsedWhereClause, lastSelectionArgs, null, delCount)); + } + } else { + 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 " + + 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 FotoSql.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); + + } + return delCount; + } + + /** + * execRenameFolder(getActivity(),"/storage/sdcard0/testFolder/", "/storage/sdcard0/renamedFolder/") + * "/storage/sdcard0/testFolder/image.jpg" becomes "/storage/sdcard0/renamedFolder/image.jpg" + * + * @return number of updated items + */ + private static int _del_execRenameFolder_batch_not_working(Context context, String pathOld, String pathNew) { + final String dbgContext = "FotoSql.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 + "))"; + + final String sqlColNewPathAlias = "new_path"; + final String sql_col_pathnew = "'" + pathNew + "' || substr(" + FotoSql.SQL_COL_PATH + + "," + (pathOld.length() + 1) + ",255) AS " + sqlColNewPathAlias; + + QueryParameter queryAffectedFiles = new QueryParameter() + .setID(FotoSql.QUERY_TYPE_DEFAULT) + .addColumn(FotoSql.SQL_COL_PK, + sql_col_pathnew) + .addFrom(FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME) + .addWhere(FotoSql.SQL_COL_PATH + " like '" + pathOld + "%'") + // SQL_COL_EXT_MEDIA_TYPE IS NOT NULL enshures that all media types (mp3, mp4, txt,...) are updated + .addWhere(FotoSql.SQL_COL_EXT_MEDIA_TYPE + " IS NOT NULL"); + + ArrayList ops = new ArrayList(); + + Cursor c = null; + try { + c = createCursorForQuery(null, dbgContext, context, queryAffectedFiles, null); + int pkColNo = c.getColumnIndex(FotoSql.SQL_COL_PK); + int pathColNo = c.getColumnIndex(sqlColNewPathAlias); + + while (c.moveToNext()) { + // paths[row] = c.getString(pathColNo); + // ids[row] = c.getLong(pkColNo); + ops.add(ContentProviderOperation.newUpdate(FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE) + .withSelection(FotoSql.FILTER_COL_PK, new String[]{c.getString(pkColNo)}) + .withValue(FotoSql.SQL_COL_PATH, c.getString(pathColNo)) + .build()); + } + } catch (Exception ex) { + Log.e(Global.LOG_CONTEXT, dbgContext + "-getAffected error :", ex); + return -1; + } finally { + if (c != null) c.close(); + } + + try { + context.getContentResolver().applyBatch(FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, ops); + } catch (Exception ex) { + // java.lang.IllegalArgumentException: Unknown authority content://media/external/file + // i assume not batch support for file + Log.e(Global.LOG_CONTEXT, dbgContext + "-updateAffected error :", ex); + return -1; + } + return ops.size(); + } + + public static ContentValues getDbContent(Context context, final long id) { + ContentResolver resolver = context.getContentResolver(); + + Cursor c = null; + try { + c = resolver.query(FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE, new String[]{"*"}, FotoSql.FILTER_COL_PK, new String[]{"" + id}, null); + if (c.moveToNext()) { + ContentValues values = new ContentValues(); + DatabaseUtils.cursorRowToContentValues(c, values); + return values; + } + } catch (Exception ex) { + Log.e(Global.LOG_CONTEXT, "FotoSql.getDbContent(id=" + id + ") failed", ex); + } finally { + if (c != null) c.close(); + } + return 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 12327a67..f33f5ae7 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 @@ -19,7 +19,6 @@ package de.k3b.android.androFotoFinder.queries; -import android.app.Activity; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; @@ -37,13 +36,21 @@ */ public class DatabaseHelper extends SQLiteOpenHelper { public static final int DATABASE_VERSION_1_TransactionLog = 1; + public static final int DATABASE_VERSION_2_MEDIA_DB_COPY = 2; - public static final int DATABASE_VERSION = DatabaseHelper.DATABASE_VERSION_1_TransactionLog; + public static final int DATABASE_VERSION = DatabaseHelper.DATABASE_VERSION_2_MEDIA_DB_COPY; public DatabaseHelper(final Context context, final String databaseName) { super(context, databaseName, null, DatabaseHelper.DATABASE_VERSION); } + public static SQLiteDatabase getWritableDatabase(Context context) { + if (instance == null) { + instance = new DatabaseHelper(new DatabaseContext(context), "APhotoManager"); + } + return instance.getWritableDatabase(); + } + /** * called if database doesn-t exist yet */ @@ -51,7 +58,7 @@ public DatabaseHelper(final Context context, final String databaseName) { public void onCreate(final SQLiteDatabase db) { db.execSQL(TransactionLogSql.CREATE_TABLE); - this.version3Upgrade_TIMESLICE_WITH_NOTES(db); + this.version2Upgrade_MediDbCopy(db); } @Override @@ -59,24 +66,16 @@ public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { Log.w(this.getClass().toString(), "Upgrading database from version " + oldVersion + " to " + newVersion + ". (Old data is kept.)"); - if (oldVersion < DatabaseHelper.DATABASE_VERSION_1_TransactionLog) { - this.version3Upgrade_TIMESLICE_WITH_NOTES(db); + if (oldVersion < DatabaseHelper.DATABASE_VERSION_2_MEDIA_DB_COPY) { + this.version2Upgrade_MediDbCopy(db); } } - private void version3Upgrade_TIMESLICE_WITH_NOTES(final SQLiteDatabase db) { - // added timeslice.notes - /* - db.execSQL("ALTER TABLE " + TransactionLogSql.TABLE - + " ADD COLUMN " + TransactionLogSql.COL_NOTES + " TEXT"); - */ - } - private static DatabaseHelper instance = null; - public static SQLiteDatabase getWritableDatabase(Activity context) { - if (instance == null) { - instance = new DatabaseHelper(new DatabaseContext(context), "APhotoManager"); + + private void version2Upgrade_MediDbCopy(final SQLiteDatabase db) { + for (String sql : MediaImageDbReplacement.DDL) { + db.execSQL(sql); } - return instance.getWritableDatabase(); } } 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 f5e54f5a..a725ba0f 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 @@ -20,8 +20,6 @@ package de.k3b.android.androFotoFinder.queries; import android.app.Activity; -import android.content.ContentProviderOperation; -import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.CursorLoader; @@ -108,6 +106,7 @@ public class FotoSql extends FotoSqlBase { public static final String SQL_COL_DISPLAY_TEXT = "disp_txt"; public static final String SQL_COL_LAT = MediaStore.Images.Media.LATITUDE; public static final String SQL_COL_LON = MediaStore.Images.Media.LONGITUDE; + public static final String SQL_COL_EXT_TITLE = MediaStore.Images.Media.TITLE; // new col id for with since ver 0.6.3 public static final String SQL_COL_WIDTH = "col_width"; @@ -133,10 +132,12 @@ public class FotoSql extends FotoSqlBase { // either code 0..8 or rotation angle 0, 90, 180, 270 public static final String SQL_COL_ORIENTATION = MediaStore.Images.ImageColumns.ORIENTATION; + public static final String SQL_COL__IMPL_DISPLAY_NAME = MediaStore.Images.Media.DISPLAY_NAME; + // only works with api >= 16 public static final String SQL_COL_MAX_WITH = - // #155: android10 incompatibility: check if the sqLite-max() function is the problem - (false && (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)) + + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) ? "max(" + MediaStore.Images.Media.WIDTH + "," + MediaStore.Images.Media.HEIGHT +")" : "1024"; @@ -153,7 +154,7 @@ public class FotoSql extends FotoSqlBase { protected static final String FILTER_EXPR_PRIVATE = "(" + SQL_COL_EXT_MEDIA_TYPE + " = " + MEDIA_TYPE_IMAGE_PRIVATE + ")"; - protected static final String FILTER_EXPR_PRIVATE_PUBLIC + public static final String FILTER_EXPR_PRIVATE_PUBLIC = "(" + SQL_COL_EXT_MEDIA_TYPE + " in (" + MEDIA_TYPE_IMAGE_PRIVATE + "," + MEDIA_TYPE_IMAGE +"))"; protected static final String FILTER_EXPR_PUBLIC = "(" + SQL_COL_EXT_MEDIA_TYPE + " = " + MEDIA_TYPE_IMAGE + ")"; @@ -205,7 +206,7 @@ public class FotoSql extends FotoSqlBase { .addGroupBy(SQL_EXPR_DAY_MODIFIED) .addOrderBy(SQL_EXPR_DAY_MODIFIED); - public static final String SQL_EXPR_FOLDER = "substr(" + SQL_COL_PATH + ",1,length(" + SQL_COL_PATH + ") - length(" + MediaStore.Images.Media.DISPLAY_NAME + "))"; + public static final String SQL_EXPR_FOLDER = "substr(" + SQL_COL_PATH + ",1,length(" + SQL_COL_PATH + ") - length(" + SQL_COL__IMPL_DISPLAY_NAME + "))"; public static final QueryParameter queryGroupByDir = new QueryParameter() .setID(QUERY_TYPE_GROUP_ALBUM) .addColumn( @@ -267,7 +268,7 @@ public class FotoSql extends FotoSqlBase { /** to avoid cascade delete of linked file when mediaDB-item is deleted * the links are first set to null before delete. */ - private static final String DELETED_FILE_MARKER = null; + public static final String DELETED_FILE_MARKER = null; /** * translate from bytes to kilobytes @@ -707,7 +708,7 @@ public static QueryParameter setSort(QueryParameter result, int sortID, boolean case SORT_BY_LOCATION_OLD: case SORT_BY_LOCATION: - return result.replaceOrderBy(SQL_COL_GPS + asc, MediaStore.Images.Media.LATITUDE + asc); + return result.replaceOrderBy(SQL_COL_GPS + asc, SQL_COL_LAT + asc); case SORT_BY_NAME_LEN_OLD: case SORT_BY_NAME_LEN: return result.replaceOrderBy("length(" + SQL_COL_PATH + ")" + asc, SQL_COL_PATH + asc); @@ -757,7 +758,7 @@ public static boolean set(GalleryFilterParameter dest, String selectedAbsolutePa public static String execGetFotoPath(Context context, Uri uriWithID) { Cursor c = null; try { - c = createCursorForQuery(null, "execGetFotoPath(uri)", context, uriWithID.toString(), null, null, null, FotoSql.SQL_COL_PATH); + c = ContentProviderMediaExecuter.createCursorForQuery(null, "execGetFotoPath(uri)", context, uriWithID.toString(), null, null, null, FotoSql.SQL_COL_PATH); if (c.moveToFirst()) { return DBUtils.getString(c,FotoSql.SQL_COL_PATH, null); } @@ -775,7 +776,7 @@ public static List execGetFotoPaths(Context context, String pathFilter) Cursor c = null; try { - c = createCursorForQuery(null, "execGetFotoPaths(pathFilter)", context,SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, + c = ContentProviderMediaExecuter.createCursorForQuery(null, "execGetFotoPaths(pathFilter)", context, SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, FotoSql.SQL_COL_PATH + " like ? and " + FILTER_EXPR_PRIVATE_PUBLIC, new String[]{pathFilter}, FotoSql.SQL_COL_PATH, FotoSql.SQL_COL_PATH); while (c.moveToNext()) { @@ -793,43 +794,6 @@ public static List execGetFotoPaths(Context context, String pathFilter) return result; } - public static Cursor createCursorForQuery( - StringBuilder out_debugMessage, String dbgContext, final Context context, - QueryParameter parameters, VISIBILITY visibility) { - if (visibility != null) setWhereVisibility(parameters, visibility); - return createCursorForQuery(out_debugMessage, dbgContext, context, parameters.toFrom(), parameters.toAndroidWhere(), - parameters.toAndroidParameters(), parameters.toOrderBy(), - parameters.toColumns() - ); - } - - /** every cursor query should go through this. adds logging if enabled */ - private 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) { - ContentResolver resolver = context.getContentResolver(); - Cursor query = null; - - Exception excpetion = null; - try { - 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" , - QueryParameter.toString(sqlSelectColums, null, from, sqlWhereStatement, - sqlWhereParameters, sqlSortOrder, query.getCount())); - if (out_debugMessage == null) { - Log.i(Global.LOG_CONTEXT, message.toString(), excpetion); - } // else logging is done by caller - } - } - - return query; - } - public static IGeoRectangle execGetGeoRectangle(StringBuilder out_debugMessage, Context context, QueryParameter baseQuery, SelectedItems selectedItems, Object... dbgContext) { StringBuilder debugMessage = (out_debugMessage == null) @@ -859,7 +823,7 @@ public static IGeoRectangle execGetGeoRectangle(StringBuilder out_debugMessage, GeoRectangle result = null; Cursor c = null; try { - c = createCursorForQuery(debugMessage, "execGetGeoRectangle", context, query, VISIBILITY.PRIVATE_PUBLIC); + c = ContentProviderMediaExecuter.createCursorForQuery(debugMessage, "execGetGeoRectangle", context, query, VISIBILITY.PRIVATE_PUBLIC); if (c.moveToFirst()) { result = new GeoRectangle(); result.setLatitude(c.getDouble(0), c.getDouble(1)); @@ -903,7 +867,7 @@ public static IGeoPoint execGetPosition(StringBuilder out_debugMessage, Context GeoPoint result = null; Cursor c = null; try { - c = createCursorForQuery(debugMessage, "execGetPosition", context, query, VISIBILITY.PRIVATE_PUBLIC); + c = ContentProviderMediaExecuter.createCursorForQuery(debugMessage, "execGetPosition", context, query, VISIBILITY.PRIVATE_PUBLIC); if (c.moveToFirst()) { result = new GeoPoint(c.getDouble(0),c.getDouble(1)); return result; @@ -938,7 +902,7 @@ public static Map execGetPathIdMap(Context context, String... file Cursor c = null; try { - c = createCursorForQuery(null, "execGetPathIdMap", context, query, null); + c = ContentProviderMediaExecuter.createCursorForQuery(null, "execGetPathIdMap", context, query, null); while (c.moveToNext()) { result.put(c.getString(1),c.getLong(0)); } @@ -972,14 +936,6 @@ public static String getWhereInFileNames(String... fileNames) { return null; } - public static int execUpdate(String dbgContext, Context context, long id, ContentValues values) { - return exexUpdateImpl(dbgContext, context, values, 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, getFilterExprPathLikeWithVisibility(visibility), new String[]{path}); - } - /** * execRenameFolder(getActivity(),"/storage/sdcard0/testFolder/", "/storage/sdcard0/renamedFolder/") * "/storage/sdcard0/testFolder/image.jpg" becomes "/storage/sdcard0/renamedFolder/image.jpg" @@ -1016,91 +972,13 @@ public static int execRenameFolder(Context context, String pathOld, String pathN for (int i = 0; i < ids.length; i++) { values.put(SQL_COL_PATH, paths[i]); selectionArgs[0] = ids[i].toString(); - if (exexUpdateImpl(_dbgContext, context, values, FILTER_COL_PK, selectionArgs) < 0) return -1; + if (ContentProviderMediaExecuter.exexUpdateImpl(_dbgContext, context, values, FILTER_COL_PK, selectionArgs) < 0) + return -1; _dbgContext = null; } return ids.length; } - /** - * execRenameFolder(getActivity(),"/storage/sdcard0/testFolder/", "/storage/sdcard0/renamedFolder/") - * "/storage/sdcard0/testFolder/image.jpg" becomes "/storage/sdcard0/renamedFolder/image.jpg" - * @return number of updated items - */ - private static int _del_execRenameFolder_batch_not_working(Context context, String pathOld, String pathNew) { - final String dbgContext = "FotoSql.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 + "))"; - - final String sqlColNewPathAlias = "new_path"; - final String sql_col_pathnew = "'" + pathNew + "' || substr(" + SQL_COL_PATH + - "," + (pathOld.length() + 1) + ",255) AS " + sqlColNewPathAlias; - - QueryParameter queryAffectedFiles = new QueryParameter() - .setID(QUERY_TYPE_DEFAULT) - .addColumn(SQL_COL_PK, - sql_col_pathnew) - .addFrom(SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME) - .addWhere(SQL_COL_PATH + " like '" + pathOld + "%'") - // SQL_COL_EXT_MEDIA_TYPE IS NOT NULL enshures that all media types (mp3, mp4, txt,...) are updated - .addWhere(SQL_COL_EXT_MEDIA_TYPE + " IS NOT NULL") - ; - - ArrayList ops = new ArrayList(); - - Cursor c = null; - try { - c = createCursorForQuery(null, dbgContext, context, queryAffectedFiles, null); - int pkColNo = c.getColumnIndex(FotoSql.SQL_COL_PK); - int pathColNo = c.getColumnIndex(sqlColNewPathAlias); - - while (c.moveToNext()) { - // paths[row] = c.getString(pathColNo); - // ids[row] = c.getLong(pkColNo); - ops.add(ContentProviderOperation.newUpdate(SQL_TABLE_EXTERNAL_CONTENT_URI_FILE) - .withSelection(FILTER_COL_PK, new String[]{c.getString(pkColNo)}) - .withValue(SQL_COL_PATH, c.getString(pathColNo)) - .build()); - } - } catch (Exception ex) { - Log.e(Global.LOG_CONTEXT, dbgContext + "-getAffected error :", ex); - return -1; - } finally { - if (c != null) c.close(); - } - - try { - context.getContentResolver().applyBatch(SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, ops); - } catch (Exception ex) { - // java.lang.IllegalArgumentException: Unknown authority content://media/external/file - // i assume not batch support for file - Log.e(Global.LOG_CONTEXT, dbgContext + "-updateAffected error :", ex); - return -1; - } - return ops.size(); - } - - /** every database update should go through this. adds logging if enabled */ - protected static int exexUpdateImpl(String dbgContext, Context context, ContentValues values, String sqlWhere, String[] selectionArgs) { - int result = -1; - Exception excpetion = null; - try { - result = context.getContentResolver().update(SQL_TABLE_EXTERNAL_CONTENT_URI_FILE, - values, sqlWhere, - selectionArgs); - } catch (Exception ex) { - excpetion = ex; - } finally { - if ((excpetion != null) || ((dbgContext != null) && (Global.debugEnabledSql || LibGlobal.debugEnabledJpg))) { - Log.i(Global.LOG_CONTEXT, dbgContext + ":FotoSql.exexUpdate " + excpetion + "\n" + - QueryParameter.toString(null, values.toString(), SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, - sqlWhere, selectionArgs, null, result), excpetion); - } - } - return result; - } - protected static String getFilterExprPathLikeWithVisibility(VISIBILITY visibility) { // visibility VISIBILITY.PRIVATE_PUBLIC String resultExpression = FotoSql.FILTER_EXPR_PATH_LIKE; @@ -1110,46 +988,6 @@ protected static String getFilterExprPathLikeWithVisibility(VISIBILITY visibilit return resultExpression; } - /** return id of inserted item */ - public static Long insertOrUpdateMediaDatabase(String dbgContext, Context context, - String dbUpdateFilterJpgFullPathName, - ContentValues values, VISIBILITY visibility, - Long updateSuccessValue) { - Long result = updateSuccessValue; - - int modifyCount = FotoSql.execUpdate(dbgContext, context, dbUpdateFilterJpgFullPathName, - values, visibility); - - if (modifyCount == 0) { - // update failed (probably becauce oldFullPathName not found. try insert it. - FotoSql.addDateAdded(values); - - Uri uriWithId = FotoSql.execInsert(dbgContext, context, values); - result = getId(uriWithId); - } - return result; - } - - /** every database insert should go through this. adds logging if enabled */ - public static Uri execInsert(String dbgContext, Context context, ContentValues values) { - Uri providerUri = (null != values.get(SQL_COL_EXT_MEDIA_TYPE)) ? SQL_TABLE_EXTERNAL_CONTENT_URI_FILE : SQL_TABLE_EXTERNAL_CONTENT_URI; - - Uri result = null; - Exception excpetion = null; - try { - // on my android-4.4 insert with media_type=1001 (private) does insert with media_type=1 (image) - result = context.getContentResolver().insert(providerUri, values); - } catch (Exception ex) { - excpetion = ex; - } finally { - if ((excpetion != null) || Global.debugEnabledSql || LibGlobal.debugEnabledJpg) { - Log.i(Global.LOG_CONTEXT, dbgContext + ":FotoSql.execInsert " + excpetion + " " + - values.toString() + " => " + result + " " + excpetion, excpetion); - } - } - return result; - } - @NonNull public static CursorLoader createCursorLoader(Context context, final QueryParameter query) { FotoSql.setWhereVisibility(query, VISIBILITY.DEFAULT); @@ -1158,7 +996,7 @@ public static CursorLoader createCursorLoader(Context context, final QueryParame } public static int execDeleteByPath(String dbgContext, Activity context, String parentDirString, VISIBILITY visibility) { - int delCount = FotoSql.deleteMedia(dbgContext, context, getFilterExprPathLikeWithVisibility(visibility), new String[] {parentDirString + "/%"}, true); + int delCount = ContentProviderMediaExecuter.deleteMedia(dbgContext, context, getFilterExprPathLikeWithVisibility(visibility), new String[]{parentDirString + "/%"}, true); return delCount; } @@ -1166,7 +1004,7 @@ public static int deleteMedia(String dbgContext, Context context, List p boolean preventDeleteImageFile) { if ((pathsToBeRemoved != null) && (pathsToBeRemoved.size() > 0)) { String whereDelete = SQL_COL_PATH + " in ('" + ListUtils.toString("','", pathsToBeRemoved) + "')"; - return deleteMedia(dbgContext, context, whereDelete, null, preventDeleteImageFile); + return ContentProviderMediaExecuter.deleteMedia(dbgContext, context, whereDelete, null, preventDeleteImageFile); } return 0; } @@ -1185,57 +1023,10 @@ public static int deleteMediaWithNullPath(Context context) { QueryParameter whereInIds = new QueryParameter(); FotoSql.setWhereSelectionPks(whereInIds, pksAsString); - return deleteMedia("delete without path (_data = null)", context, whereInIds.toAndroidWhere(), null, true); + return ContentProviderMediaExecuter.deleteMedia("delete without path (_data = null)", context, whereInIds.toAndroidWhere(), null, true); } return 0; } - /** - * Deletes media items specified by where with the option to prevent cascade delete of the image. - */ - public static int deleteMedia(String dbgContext, Context context, 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, 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: ", - context, values, lastUsedWhereClause, lastSelectionArgs); - - lastUsedWhereClause = FotoSql.SQL_COL_PATH + " is null"; - lastSelectionArgs = null; - delCount = context.getContentResolver() - .delete(SQL_TABLE_EXTERNAL_CONTENT_URI_FILE, lastUsedWhereClause, lastSelectionArgs); - if (Global.debugEnabledSql || LibGlobal.debugEnabledJpg) { - Log.i(Global.LOG_CONTEXT, dbgContext + "-b: FotoSql.deleteMedia delete\n" + - QueryParameter.toString(null, null, SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, - lastUsedWhereClause, lastSelectionArgs, null, delCount)); - } - } else { - delCount = context.getContentResolver() - .delete(SQL_TABLE_EXTERNAL_CONTENT_URI_FILE, lastUsedWhereClause, lastSelectionArgs); - if (Global.debugEnabledSql || LibGlobal.debugEnabledJpg) { - Log.i(Global.LOG_CONTEXT, dbgContext +": FotoSql.deleteMedia\ndelete " + - QueryParameter.toString(null, null, - 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 FotoSql.deleteMedia:\n" + - QueryParameter.toString(null, null, SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, - lastUsedWhereClause, lastSelectionArgs, null, -1) - + " : " + ex.getMessage(); - Log.e(Global.LOG_CONTEXT, msg, ex); - - } - return delCount; - } /** converts imageID to content-uri */ public static Uri getUri(long imageID) { @@ -1290,7 +1081,7 @@ public static String getMinFolder(Context context, QueryParameter query, Cursor c = null; try { - c = FotoSql.createCursorForQuery(null, "getCount", context, queryModified, null); + c = ContentProviderMediaExecuter.createCursorForQuery(null, "getCount", context, queryModified, null); if (c.moveToNext()) { return c.getString(0); } @@ -1309,7 +1100,7 @@ public static long getCount(Context context, QueryParameter query) { Cursor c = null; try { - c = FotoSql.createCursorForQuery(null, "getCount", context, queryModified, null); + c = ContentProviderMediaExecuter.createCursorForQuery(null, "getCount", context, queryModified, null); if (c.moveToNext()) { return c.getLong(0); } @@ -1338,7 +1129,7 @@ public static CharSequence getStatisticsMessage(Context context, int prefixStrin Cursor c = null; try { - c = FotoSql.createCursorForQuery(null, "getCount", context, queryModified, null); + c = ContentProviderMediaExecuter.createCursorForQuery(null, "getCount", context, queryModified, null); if (c.moveToNext()) { final long count = c.getLong(0); long size = c.getLong(1); @@ -1374,7 +1165,7 @@ private static SelectedFiles getSelectedfiles(Context context, QueryParameter qu Cursor c = null; try { - c = FotoSql.createCursorForQuery(null, "getSelectedfiles", context, query, visibility); + c = ContentProviderMediaExecuter.createCursorForQuery(null, "getSelectedfiles", context, query, visibility); int len = c.getCount(); Long[] ids = new Long[len]; String[] paths = new String[len]; @@ -1448,7 +1239,7 @@ private static List getFileNamesImpl(Context context, QueryParameter par Cursor cursor = null; try { - cursor = createCursorForQuery(null, "getFileNames", context, parameters, VISIBILITY.PRIVATE_PUBLIC); + cursor = ContentProviderMediaExecuter.createCursorForQuery(null, "getFileNames", context, parameters, VISIBILITY.PRIVATE_PUBLIC); int colPath = cursor.getColumnIndex(SQL_COL_DISPLAY_TEXT); if (colPath == -1) colPath = cursor.getColumnIndex(SQL_COL_PATH); @@ -1526,7 +1317,7 @@ public static List getAlbumFiles(Context context, String path, int subDi public static int execRename(Context context, String oldFullPath, String newFullPath) { ContentValues values = new ContentValues(); values.put(SQL_COL_PATH, newFullPath); - return FotoSql.execUpdate("rename file", context, oldFullPath, + return ContentProviderMediaExecuter.execUpdate("rename file", context, oldFullPath, values, null); } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/FotoThumbSql.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/FotoThumbSql.java index 137eb258..b9b00c78 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/FotoThumbSql.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/FotoThumbSql.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 by k3b. + * Copyright (c) 2015-2019 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -66,7 +66,7 @@ private static String getStatistic(Context context, QueryParameter query, String Cursor c = null; try { - c = FotoSql.createCursorForQuery(null, mDebugPrefix + "getStatistic", context, query, VISIBILITY.PRIVATE_PUBLIC); + c = ContentProviderMediaExecuter.createCursorForQuery(null, mDebugPrefix + "getStatistic", context, query, VISIBILITY.PRIVATE_PUBLIC); if (Global.debugEnabledSql) { Log.i(Global.LOG_CONTEXT, mDebugPrefix + "getStatistic " + c.getCount() + "\n\t" + query.toSqlString()); 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 new file mode 100644 index 00000000..4818a043 --- /dev/null +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaImageDbReplacement.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2015-2019 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see + */ +package de.k3b.android.androFotoFinder.queries; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.provider.MediaStore; + +import java.sql.Date; + +import de.k3b.android.androFotoFinder.Global; +import de.k3b.database.QueryParameter; +import de.k3b.io.AlbumFile; + +import static de.k3b.android.androFotoFinder.queries.FotoSql.QUERY_TYPE_UNDEFINED; +import static de.k3b.android.androFotoFinder.queries.FotoSql.SQL_COL_DATE_ADDED; +import static de.k3b.android.androFotoFinder.queries.FotoSql.SQL_COL_DATE_TAKEN; +import static de.k3b.android.androFotoFinder.queries.FotoSql.SQL_COL_EXT_MEDIA_TYPE; +import static de.k3b.android.androFotoFinder.queries.FotoSql.SQL_COL_EXT_RATING; +import static de.k3b.android.androFotoFinder.queries.FotoSql.SQL_COL_EXT_TITLE; +import static de.k3b.android.androFotoFinder.queries.FotoSql.SQL_COL_LAST_MODIFIED; +import static de.k3b.android.androFotoFinder.queries.FotoSql.SQL_COL_LAT; +import static de.k3b.android.androFotoFinder.queries.FotoSql.SQL_COL_LON; +import static de.k3b.android.androFotoFinder.queries.FotoSql.SQL_COL_ORIENTATION; +import static de.k3b.android.androFotoFinder.queries.FotoSql.SQL_COL_PATH; +import static de.k3b.android.androFotoFinder.queries.FotoSql.SQL_COL_PK; +import static de.k3b.android.androFotoFinder.queries.FotoSql.SQL_COL_SIZE; +import static de.k3b.android.androFotoFinder.queries.FotoSql.SQL_COL__IMPL_DISPLAY_NAME; +import static de.k3b.android.androFotoFinder.queries.FotoSql.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME; +import static de.k3b.android.androFotoFinder.tagDB.TagSql.SQL_COL_EXT_DESCRIPTION; +import static de.k3b.android.androFotoFinder.tagDB.TagSql.SQL_COL_EXT_TAGS; +import static de.k3b.android.androFotoFinder.tagDB.TagSql.SQL_COL_EXT_XMP_LAST_MODIFIED_DATE; + +/** + * 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 { + /** + * 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)", + "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 Object get(Cursor cursor, columIndex) + + 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 = ContentProviderMediaExecuter.createCursorForQuery(null, "execGetFotoPaths(pathFilter)", context, + query, 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 949bac19..4a580e90 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 by k3b. + * Copyright (c) 2015-2019 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -21,7 +21,6 @@ import android.app.Activity; import android.database.Cursor; -import android.net.Uri; import android.os.AsyncTask; import android.util.Log; @@ -66,8 +65,9 @@ protected SelectedItems doInBackground(QueryParameter... querys) { Cursor cursor = null; try { - cursor = mContext.getContentResolver().query(Uri.parse(query.toFrom()), query.toColumns(), - query.toAndroidWhere(), query.toAndroidParameters(), query.toOrderBy()); + cursor = ContentProviderMediaExecuter.createCursorForQuery( + null, "SqlJobTask", mContext, + query, 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 4dee2740..a9d7c4db 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 @@ -19,11 +19,9 @@ package de.k3b.android.androFotoFinder.tagDB; -import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; -import android.database.DatabaseUtils; import android.provider.MediaStore; import android.util.Log; @@ -36,6 +34,7 @@ import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.media.PhotoPropertiesMediaDBContentValues; import de.k3b.android.androFotoFinder.queries.AndroidAlbumUtils; +import de.k3b.android.androFotoFinder.queries.ContentProviderMediaExecuter; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.util.PhotoPropertiesMediaFilesScanner; import de.k3b.database.QueryParameter; @@ -61,7 +60,6 @@ public class TagSql extends FotoSql { public static final String SQL_COL_EXT_TAGS = MediaStore.Video.Media.TAGS; public static final String SQL_COL_EXT_DESCRIPTION = MediaStore.Images.Media.DESCRIPTION; - public static final String SQL_COL_EXT_TITLE = MediaStore.Images.Media.TITLE; /** The date & time when last non standard media-scan took place *

Type: INTEGER (long) as milliseconds since jan 1, 1970

*/ @@ -275,7 +273,7 @@ public static int fixPrivate(Context context) { PhotoPropertiesUtil.IMG_TYPE_PRIVATE + "'")); } where.append(")"); - return exexUpdateImpl("Fix visibility private", context, + return ContentProviderMediaExecuter.exexUpdateImpl("Fix visibility private", context, values, where.toString(), new String[] {"%;" + VISIBILITY.TAG_PRIVATE + ";%"}); } @@ -327,25 +325,6 @@ public static void setFileModifyDate(ContentValues values, long fileModifyDateSe } } - public static ContentValues getDbContent(Context context, final long id) { - ContentResolver resolver = context.getContentResolver(); - - Cursor c = null; - try { - c = resolver.query(SQL_TABLE_EXTERNAL_CONTENT_URI_FILE, new String[]{"*"}, FILTER_COL_PK, new String[]{"" + id}, null); - if (c.moveToNext()) { - ContentValues values = new ContentValues(); - DatabaseUtils.cursorRowToContentValues(c, values); - return values; - } - } catch (Exception ex) { - Log.e(Global.LOG_CONTEXT, "FotoSql.getDbContent(id=" + id + ") failed", ex); - } finally { - if (c != null) c.close(); - } - return null; - } - /** * Copies non null content of jpg to existing media database item. * @@ -391,9 +370,9 @@ public static int updateDB(String dbgContext, Context context, String oldFullJpg public static int execUpdate(String dbgContext, Context context, String path, long xmpFileDate, ContentValues values, VISIBILITY visibility) { if ((!Global.Media.enableXmpNone) || (xmpFileDate == EXT_LAST_EXT_SCAN_UNKNOWN)) { - return execUpdate(dbgContext, context, path, values, visibility); + return ContentProviderMediaExecuter.execUpdate(dbgContext, context, path, values, visibility); } - return exexUpdateImpl(dbgContext, context, values, FILTER_EXPR_PATH_LIKE_XMP_DATE, new String[]{path, Long.toString(xmpFileDate)}); + return ContentProviderMediaExecuter.exexUpdateImpl(dbgContext, context, values, FILTER_EXPR_PATH_LIKE_XMP_DATE, new String[]{path, Long.toString(xmpFileDate)}); } /** return how many photos exist that have one or more tags from list */ @@ -403,7 +382,7 @@ public static int getTagRefCount(Context context, List tags) { if (addWhereAnyOfTags(query, tags) > 0) { Cursor c = null; try { - c = createCursorForQuery(null, "getTagRefCount", context, query, VISIBILITY.PRIVATE_PUBLIC); + c = ContentProviderMediaExecuter.createCursorForQuery(null, "getTagRefCount", context, query, VISIBILITY.PRIVATE_PUBLIC); if (c.moveToFirst()) { return c.getInt(0); } @@ -457,7 +436,7 @@ public static List loadTagWorflowItems(Context context, String s if (filterCount > 0) { try { - c = createCursorForQuery(null, "loadTagWorflowItems", context, query, VISIBILITY.PRIVATE_PUBLIC); + c = ContentProviderMediaExecuter.createCursorForQuery(null, "loadTagWorflowItems", context, query, VISIBILITY.PRIVATE_PUBLIC); 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/AndroidFileCommands.java b/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java index 392ee504..72f04c3a 100644 --- a/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java +++ b/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java @@ -42,6 +42,7 @@ import de.k3b.android.androFotoFinder.R; import de.k3b.android.androFotoFinder.directory.DirectoryPickerFragment; import de.k3b.android.androFotoFinder.media.AndroidPhotoPropertiesBulkUpdateService; +import de.k3b.android.androFotoFinder.queries.ContentProviderMediaExecuter; import de.k3b.android.androFotoFinder.queries.DatabaseHelper; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.androFotoFinder.tagDB.TagSql; @@ -328,7 +329,7 @@ public int deleteFiles(SelectedFiles fotos, IProgessListener progessListener) { QueryParameter where = new QueryParameter(); FotoSql.setWhereSelectionPks (where, fotos.toIdString()); - FotoSql.deleteMedia("AndroidFileCommands.deleteFiles", mContext, where.toAndroidWhere(), null, true); + ContentProviderMediaExecuter.deleteMedia("AndroidFileCommands.deleteFiles", mContext, where.toAndroidWhere(), null, true); } return deleteCount; } 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 8b620476..2388e523 100644 --- a/app/src/main/java/de/k3b/android/util/PhotoPropertiesMediaFilesScanner.java +++ b/app/src/main/java/de/k3b/android/util/PhotoPropertiesMediaFilesScanner.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2018 by k3b. + * Copyright (c) 2015-2019 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -44,6 +44,7 @@ import de.k3b.LibGlobal; import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.media.PhotoPropertiesMediaDBContentValues; +import de.k3b.android.androFotoFinder.queries.ContentProviderMediaExecuter; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.androFotoFinder.tagDB.TagSql; import de.k3b.database.QueryParameter; @@ -52,9 +53,9 @@ import de.k3b.io.FileUtils; import de.k3b.io.VISIBILITY; import de.k3b.media.IPhotoProperties; +import de.k3b.media.PhotoPropertiesChainReader; import de.k3b.media.PhotoPropertiesUtil; import de.k3b.media.PhotoPropertiesXmpSegment; -import de.k3b.media.PhotoPropertiesChainReader; import de.k3b.tagDB.TagRepository; /** @@ -75,15 +76,15 @@ abstract public class PhotoPropertiesMediaFilesScanner { protected static final String DB_LATITUDE = MediaStore.Images.Media.LATITUDE; */ - private static final String DB_SIZE = MediaStore.MediaColumns.SIZE; + protected static final String DB_TITLE = FotoSql.SQL_COL_EXT_TITLE; private static final String DB_WIDTH = MediaStore.MediaColumns.WIDTH; private static final String DB_HEIGHT = MediaStore.MediaColumns.HEIGHT; private static final String DB_MIME_TYPE = MediaStore.MediaColumns.MIME_TYPE; protected static final String DB_ORIENTATION = MediaStore.Images.Media.ORIENTATION; - protected static final String DB_TITLE = MediaStore.MediaColumns.TITLE; + protected static final String DB_DATA = FotoSql.SQL_COL_PATH; // _data protected static final String DB_DISPLAY_NAME = MediaStore.MediaColumns.DISPLAY_NAME; - protected static final String DB_DATA = MediaStore.MediaColumns.DATA; + private static final String DB_SIZE = FotoSql.SQL_COL_SIZE; public static final int DEFAULT_SCAN_DEPTH = 22; public static final String MEDIA_IGNORE_FILENAME = FileUtils.MEDIA_IGNORE_FILENAME; // MediaStore.MEDIA_IGNORE_FILENAME; @@ -232,7 +233,7 @@ public Long insertOrUpdateMediaDatabase(String dbgContext, Context context, if ((currentJpgFile != null) && currentJpgFile.exists() && currentJpgFile.canRead()) { ContentValues values = createDefaultContentValues(); getExifFromFile(values, currentJpgFile); - Long result = FotoSql.insertOrUpdateMediaDatabase( + Long result = ContentProviderMediaExecuter.insertOrUpdateMediaDatabase( dbgContext, context, dbUpdateFilterJpgFullPathName, values, VISIBILITY.PRIVATE_PUBLIC, updateSuccessValue); @@ -248,7 +249,7 @@ private int deleteInMediaDatabase(Context context, String[] oldPathNames) { if ((oldPathNames != null) && (oldPathNames.length > 0)) { String sqlWhere = FotoSql.getWhereInFileNames(oldPathNames); try { - modifyCount = FotoSql.deleteMedia(CONTEXT + "deleteInMediaDatabase", context, sqlWhere, null, true); + modifyCount = ContentProviderMediaExecuter.deleteMedia(CONTEXT + "deleteInMediaDatabase", context, sqlWhere, null, true); if (Global.debugEnabled) { Log.d(Global.LOG_CONTEXT, CONTEXT + "deleteInMediaDatabase(len=" + oldPathNames.length + ", files='" + oldPathNames[0] + "'...) result count=" + modifyCount); } @@ -300,7 +301,7 @@ private int renameInMediaDatabase(Context context, Map old2NewFi Cursor c = null; try { - c = FotoSql.createCursorForQuery(null, "renameInMediaDatabase", context, query, VISIBILITY.PRIVATE_PUBLIC); + c = ContentProviderMediaExecuter.createCursorForQuery(null, "renameInMediaDatabase", context, query, VISIBILITY.PRIVATE_PUBLIC); int pkColNo = c.getColumnIndex(FotoSql.SQL_COL_PK); int pathColNo = c.getColumnIndex(FotoSql.SQL_COL_PATH); while (c.moveToNext()) { @@ -431,7 +432,7 @@ public int updatePathRelatedFields(Context context, Cursor cursor, String newAbs String oldAbsolutePath = cursor.getString(columnIndexPath); int id = cursor.getInt(columnIndexPk); setPathRelatedFieldsIfNeccessary(values, newAbsolutePath, oldAbsolutePath); - return FotoSql.execUpdate("updatePathRelatedFields", context, id, values); + return ContentProviderMediaExecuter.execUpdate("updatePathRelatedFields", context, id, values); } /** sets the path related fields */ @@ -463,7 +464,7 @@ private int update_Android42(String dbgContext, Context context, long id, File f if ((file != null) && file.exists() && file.canRead()) { ContentValues values = createDefaultContentValues(); getExifFromFile(values, file); - return FotoSql.execUpdate(dbgContext, context, id, values); + return ContentProviderMediaExecuter.execUpdate(dbgContext, context, id, values); } return 0; } @@ -487,7 +488,7 @@ private int insert_Android42(String dbgContext, Context context, File file) { FotoSql.addDateAdded(values); getExifFromFile(values, file); - return (null != FotoSql.execInsert(dbgContext, context, values)) ? 1 : 0; + return (null != ContentProviderMediaExecuter.execInsert(dbgContext, context, values)) ? 1 : 0; } return 0; } From 91eb4b38e1cbf8dd059767baa9124e1239b9b25f Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Thu, 28 Nov 2019 00:22:28 +0100 Subject: [PATCH 27/37] #155: prepare to fix android10 incompatibility: refactored replace all static method calls of ContentProviderMediaExecuter(ContentProviderMediaImpl) with dynamic Interface IMediaDBApi calls --- .../androFotoFinder/AdapterArrayHelper.java | 4 +- .../k3b/android/androFotoFinder/AffUtils.java | 2 +- .../androFotoFinder/AndroFotoFinderApp.java | 6 +- .../PhotoAutoprocessingEditActivity.java | 2 +- .../PhotoPropertiesEditActivity.java | 2 +- .../backup/Backup2ZipService.java | 5 +- .../backup/BackupActivity.java | 4 +- .../directory/DirectoryLoaderTask.java | 5 +- .../directory/DirectoryPickerFragment.java | 11 +- .../directory/ShowInMenuHandler.java | 4 +- .../gallery/cursor/GalleryCursorFragment.java | 5 +- .../ImageDetailActivityViewPager.java | 4 +- .../ImageDetailMetaDialogBuilder.java | 4 +- .../locationmap/LocationMapFragment.java | 4 +- .../locationmap/MapGeoPickerActivity.java | 2 +- .../locationmap/MarkerLoaderTask.java | 5 +- .../PickerLocationMapFragment.java | 2 +- ...otoPropertiesMediaDBCsvImportActivity.java | 4 +- .../queries/AndroidAlbumUtils.java | 8 +- ...ter.java => ContentProviderMediaImpl.java} | 5 +- .../androFotoFinder/queries/FotoSql.java | 105 +++++++++++------- .../androFotoFinder/queries/FotoThumbSql.java | 2 +- .../androFotoFinder/queries/IMediaDBApi.java | 65 +++++++++++ .../queries/MediaDBContentprovider.java | 105 ++++++++++++++++++ .../queries/MediaImageDbReplacement.java | 2 +- .../queries/SqlJobTaskBase.java | 4 +- .../android/androFotoFinder/tagDB/TagSql.java | 25 ++--- .../androFotoFinder/tagDB/TagWorflow.java | 12 +- .../tagDB/TagsPickerFragment.java | 6 +- .../k3b/android/util/AndroidFileCommands.java | 9 +- .../java/de/k3b/android/util/IntentUtil.java | 2 +- .../PhotoPropertiesMediaFilesScanner.java | 21 ++-- 32 files changed, 318 insertions(+), 128 deletions(-) rename app/src/main/java/de/k3b/android/androFotoFinder/queries/{ContentProviderMediaExecuter.java => ContentProviderMediaImpl.java} (98%) create mode 100644 app/src/main/java/de/k3b/android/androFotoFinder/queries/IMediaDBApi.java create mode 100644 app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBContentprovider.java diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/AdapterArrayHelper.java b/app/src/main/java/de/k3b/android/androFotoFinder/AdapterArrayHelper.java index 36a1dcd5..e0c215d7 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/AdapterArrayHelper.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/AdapterArrayHelper.java @@ -26,9 +26,9 @@ import java.util.ArrayList; import de.k3b.android.androFotoFinder.queries.FotoSql; +import de.k3b.io.FileUtils; import de.k3b.io.VISIBILITY; import de.k3b.io.collections.SelectedItems; -import de.k3b.io.FileUtils; import de.k3b.media.PhotoPropertiesUtil; /** @@ -47,7 +47,7 @@ public AdapterArrayHelper(Activity context, String fullPhotoPath, String debugCo if (Global.mustRemoveNOMEDIAfromDB && (mRootDir != null) && (mFullPhotoPaths != null)) { String parentDirString = mRootDir.getAbsolutePath(); - FotoSql.execDeleteByPath(debugContext + " AdapterArrayHelper mustRemoveNOMEDIAfromDB ", context, parentDirString, VISIBILITY.PRIVATE_PUBLIC); + FotoSql.execDeleteByPath(debugContext + " AdapterArrayHelper mustRemoveNOMEDIAfromDB ", parentDirString, VISIBILITY.PRIVATE_PUBLIC); } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/AffUtils.java b/app/src/main/java/de/k3b/android/androFotoFinder/AffUtils.java index e68386cb..8406b0b2 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/AffUtils.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/AffUtils.java @@ -122,7 +122,7 @@ public static SelectedFiles querySelectedFiles(Context context, SelectedItems it List paths = new ArrayList(); List datesPhotoTaken = new ArrayList(); - if (FotoSql.getFileNames(context, items, ids, paths, datesPhotoTaken) != null) { + if (FotoSql.getFileNames(items, ids, paths, datesPhotoTaken) != null) { return new SelectedFiles(paths.toArray(new String[paths.size()]), ids.toArray(new Long[ids.size()]), datesPhotoTaken.toArray(new Date[datesPhotoTaken.size()])); } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java index 97b76eaf..65d3e901 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java @@ -38,6 +38,7 @@ import de.k3b.android.androFotoFinder.imagedetail.HugeImageLoader; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.androFotoFinder.queries.FotoSqlBase; +import de.k3b.android.androFotoFinder.queries.MediaDBContentprovider; import de.k3b.android.osmdroid.forge.MapsForgeSupport; import de.k3b.android.util.LogCat; import de.k3b.android.widget.ActivityWithCallContext; @@ -94,6 +95,9 @@ public static RefWatcher getRefWatcher(Context context) { // StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyDeath().build()); FotoSqlBase.init(); + /// #155: todo: depending on android-api version set IMediaDBApi + FotoSql.setMediaDBApi(new MediaDBContentprovider(this)); + super.onCreate(); LibGlobal.appName = getString(R.string.app_name); @@ -165,7 +169,7 @@ private File getOutpuFile() { // #60: configure some of the mapsforge settings first MapsForgeSupport.createInstance(this); - FotoSql.deleteMediaWithNullPath(this); + FotoSql.deleteMediaWithNullPath(); Log.i(Global.LOG_CONTEXT, getAppId() + " created"); } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/PhotoAutoprocessingEditActivity.java b/app/src/main/java/de/k3b/android/androFotoFinder/PhotoAutoprocessingEditActivity.java index 06efc4c9..40fac08d 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/PhotoAutoprocessingEditActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/PhotoAutoprocessingEditActivity.java @@ -591,7 +591,7 @@ private SelectedFiles getSelectedFiles(String dbgContext, Intent intent, boolean if (itemCount > 0) { if ((mustLoadIDs) && (ids == null)) { ids = new Long[itemCount]; - Map idMap = FotoSql.execGetPathIdMap(this, fileNames); + Map idMap = FotoSql.execGetPathIdMap(fileNames); for (int i = 0; i < itemCount; i++) { ids[i] = idMap.get(fileNames[i]); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/PhotoPropertiesEditActivity.java b/app/src/main/java/de/k3b/android/androFotoFinder/PhotoPropertiesEditActivity.java index 18b176c1..d3d32f19 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/PhotoPropertiesEditActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/PhotoPropertiesEditActivity.java @@ -284,7 +284,7 @@ private static SelectedFiles getSelectedFiles(String dbgContext, Context ctx, In if (itemCount > 0) { if ((mustLoadIDs) && (ids == null)) { ids = new Long[itemCount]; - Map idMap = FotoSql.execGetPathIdMap(ctx, fileNames); + Map idMap = FotoSql.execGetPathIdMap(fileNames); for (int i = 0; i < itemCount; i++) { ids[i] = idMap.get(fileNames[i]); 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 7de70da1..fbf2c76e 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 @@ -31,7 +31,6 @@ import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.R; import de.k3b.android.androFotoFinder.media.PhotoPropertiesMediaDBCursor; -import de.k3b.android.androFotoFinder.queries.ContentProviderMediaExecuter; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.androFotoFinder.tagDB.TagSql; import de.k3b.database.QueryParameter; @@ -210,8 +209,8 @@ private void execQuery(QueryParameter query, Cursor cursor = null; try { this.onProgress(0,0, "Calculate"); - cursor = ContentProviderMediaExecuter.createCursorForQuery( - null, "ZipExecute", context, + cursor = FotoSql.getMediaDBApi().createCursorForQuery( + null, "ZipExecute", query, null); int itemCount = cursor.getCount(); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/backup/BackupActivity.java b/app/src/main/java/de/k3b/android/androFotoFinder/backup/BackupActivity.java index 70fe7acf..6197748d 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/backup/BackupActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/backup/BackupActivity.java @@ -496,7 +496,7 @@ private SelectedFiles getSelectedFiles(String dbgContext, Intent intent, boolean if (itemCount > 0) { if ((mustLoadIDs) && (ids == null)) { ids = new Long[itemCount]; - Map idMap = FotoSql.execGetPathIdMap(this, fileNames); + Map idMap = FotoSql.execGetPathIdMap(fileNames); for (int i = 0; i < itemCount; i++) { ids[i] = idMap.get(fileNames[i]); @@ -702,7 +702,7 @@ private void updateHistory(QueryParameter query) { paths.add(DCIM_ROOT); - String minFolder = FotoSql.getMinFolder(getApplicationContext(), query, true); + String minFolder = FotoSql.getMinFolder(query, true); updateHistory(FileUtils.getDir(minFolder), filenames, paths, 1); String queryFolder = FotoSql.getFilePath(query, false); 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 67df3563..eff46449 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 @@ -28,7 +28,6 @@ import java.util.List; import de.k3b.android.androFotoFinder.Global; -import de.k3b.android.androFotoFinder.queries.ContentProviderMediaExecuter; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.database.QueryParameter; import de.k3b.io.Directory; @@ -102,8 +101,8 @@ protected IDirectory doInBackground(QueryParameter... queryParameter) { } try { - cursor = ContentProviderMediaExecuter.createCursorForQuery( - null, "ZipExecute", context, + cursor = FotoSql.getMediaDBApi().createCursorForQuery( + null, "ZipExecute", queryParameters, null); int itemCount = cursor.getCount(); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryPickerFragment.java b/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryPickerFragment.java index 1806edc3..84f33d02 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryPickerFragment.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryPickerFragment.java @@ -62,7 +62,6 @@ import de.k3b.android.androFotoFinder.ThumbNailUtils; import de.k3b.android.androFotoFinder.backup.BackupActivity; import de.k3b.android.androFotoFinder.imagedetail.ImageDetailMetaDialogBuilder; -import de.k3b.android.androFotoFinder.queries.ContentProviderMediaExecuter; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.androFotoFinder.queries.FotoThumbSql; import de.k3b.android.androFotoFinder.queries.FotoViewerParameter; @@ -545,7 +544,7 @@ private void onDeleteAnswer(File file, IDirectory dir) { } // delete from database - if (FotoSql.deleteMedia("delete album", getActivity(), + if (FotoSql.deleteMedia("delete album", ListUtils.toStringList(file.getAbsolutePath()),false) > 0) { deleteSuccess = true; } @@ -664,11 +663,11 @@ private boolean fixLinks(IDirectory linkDir) { if (!canonicalPath.endsWith("/")) canonicalPath+="/"; String sqlWhereLink = FotoSql.SQL_COL_PATH + " like '" + linkPath + "%'"; - SelectedFiles linkFiles = FotoSql.getSelectedfiles(context, sqlWhereLink, VISIBILITY.PRIVATE_PUBLIC); + SelectedFiles linkFiles = FotoSql.getSelectedfiles(sqlWhereLink, VISIBILITY.PRIVATE_PUBLIC); String sqlWhereCanonical = FotoSql.SQL_COL_PATH + " in (" + linkFiles.toString() + ")"; sqlWhereCanonical = sqlWhereCanonical.replace(linkPath,canonicalPath); - SelectedFiles canonicalFiles = FotoSql.getSelectedfiles(context, sqlWhereCanonical, VISIBILITY.PRIVATE_PUBLIC); + SelectedFiles canonicalFiles = FotoSql.getSelectedfiles(sqlWhereCanonical, VISIBILITY.PRIVATE_PUBLIC); HashMap link2canonical = new HashMap(); for(String cann : canonicalFiles.getFileNames()) { link2canonical.put(linkPath + cann.substring(canonicalPath.length()), cann); @@ -693,9 +692,9 @@ private boolean fixLinks(IDirectory linkDir) { if (cann == null) { // rename linkFile to canonicalFile updateValues.put(FotoSql.SQL_COL_PATH, canonicalPath + lin.substring(linkPath.length())); - ContentProviderMediaExecuter.execUpdate("fixLinks", context, linkIds[i].intValue(), updateValues); + FotoSql.getMediaDBApi().execUpdate("fixLinks", linkIds[i].intValue(), updateValues); } else { - ContentProviderMediaExecuter.deleteMedia("DirectoryPickerFragment.fixLinks", context, FotoSql.FILTER_COL_PK, new String[]{linkIds[i].toString()}, true); + FotoSql.getMediaDBApi().deleteMedia("DirectoryPickerFragment.fixLinks", FotoSql.FILTER_COL_PK, new String[]{linkIds[i].toString()}, true); } } PhotoPropertiesMediaFilesScanner.notifyChanges(context, "Fixed link/canonical duplicates"); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/directory/ShowInMenuHandler.java b/app/src/main/java/de/k3b/android/androFotoFinder/directory/ShowInMenuHandler.java index 45a72c9a..4e0395bb 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/directory/ShowInMenuHandler.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/directory/ShowInMenuHandler.java @@ -134,7 +134,7 @@ private long getPickCount(String dbgContext, QueryParameter query = AndroidAlbumUtils.getAsAlbumOrMergedNewQuery( dbgContext, mContext, baseQuery, filter); if (query == null) return 0; - return FotoSql.getCount(mContext, query); + return FotoSql.getCount(query); } private boolean showPhoto(String dbgContext, QueryParameter baseQuery) { @@ -162,7 +162,7 @@ private boolean showMap(String dbgContext, QueryParameter baseQuery) { QueryParameter query = AndroidAlbumUtils.getAsAlbumOrMergedNewQuery( dbgContext, mContext, baseQuery, currentSelectionFilter); if (query != null) { - IGeoRectangle area = FotoSql.execGetGeoRectangle(null, mContext, query, + IGeoRectangle area = FotoSql.execGetGeoRectangle(null, query, null, "Calculate visible arean", dbgContext); MapGeoPickerActivity.showActivity(dbgContext, mContext, null, query, area, 0); 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 224b8732..0b228046 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,7 +71,6 @@ 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.ContentProviderMediaExecuter; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.androFotoFinder.queries.FotoViewerParameter; import de.k3b.android.androFotoFinder.queries.Queryable; @@ -1153,7 +1152,7 @@ private Uri getUri(Activity parent, long id) { Uri resultUri = null; if (mModePickGeoElsePickImaage) { // mode pick gep - IGeoPoint initialPoint = FotoSql.execGetPosition(null, parent, + IGeoPoint initialPoint = FotoSql.execGetPosition(null, null, id, mDebugPrefix, "getSelectedUri"); if (initialPoint != null) { @@ -1417,7 +1416,7 @@ private void onDuplicatesFound(SelectedItems selectedItems, StringBuffer debugMe String sqlWhere = query.toAndroidWhere(); // + " OR " + FotoSql.SQL_COL_PATH + " is null"; try { - delCount = ContentProviderMediaExecuter.deleteMedia(mDebugPrefix + "onDuplicatesFound", activity, sqlWhere, null, true); + delCount = FotoSql.getMediaDBApi().deleteMedia(mDebugPrefix + "onDuplicatesFound", sqlWhere, null, true); } catch (Exception ex) { Log.w(Global.LOG_CONTEXT, "deleteMedia via update failed for 'where " + sqlWhere + "'."); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java index e4cec378..a98bbc58 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java @@ -800,7 +800,7 @@ private static int updateIncompleteMediaDatabase(String debugPrefix, Context con String dbPathSearch = null; ArrayList missing = new ArrayList(); dbPathSearch = dirToScan.getPath() + "/%"; - List known = FotoSql.execGetFotoPaths(context, dbPathSearch); + List known = FotoSql.execGetFotoPaths(dbPathSearch); File[] existing = dirToScan.listFiles(); if (existing != null) { @@ -973,7 +973,7 @@ public boolean onOptionsItemSelected(MenuItem menuItem) { case R.id.cmd_show_geo_as: { final long imageId = getCurrentImageId(); - IGeoPoint _geo = FotoSql.execGetPosition(null, this, + IGeoPoint _geo = FotoSql.execGetPosition(null, null, imageId, mDebugPrefix, "on cmd_show_geo_as"); final String currentFilePath = getCurrentFilePath(); GeoPointDto geo = new GeoPointDto(_geo.getLatitude(), _geo.getLongitude(), GeoPointDto.NO_ZOOM); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailMetaDialogBuilder.java b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailMetaDialogBuilder.java index 880d922d..16e54560 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailMetaDialogBuilder.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailMetaDialogBuilder.java @@ -33,7 +33,7 @@ import java.util.Date; import java.util.List; -import de.k3b.android.androFotoFinder.queries.ContentProviderMediaExecuter; +import de.k3b.android.androFotoFinder.queries.ContentProviderMediaImpl; import de.k3b.android.androFotoFinder.tagDB.TagSql; import de.k3b.android.widget.ActivityWithCallContext; import de.k3b.database.QueryParameter; @@ -138,7 +138,7 @@ private static void appendExifInfo(StringBuilder result, Activity context, Strin if (currentImageId != 0) { - ContentValues dbContent = ContentProviderMediaExecuter.getDbContent(context, currentImageId); + ContentValues dbContent = ContentProviderMediaImpl.getDbContent(context, currentImageId); if (dbContent != null) { result.append(NL).append(line).append(NL); result.append(NL).append(TagSql.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE).append(NL).append(NL); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/LocationMapFragment.java b/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/LocationMapFragment.java index 7abacfe5..d7af20f1 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/LocationMapFragment.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/LocationMapFragment.java @@ -1120,7 +1120,7 @@ private boolean zoomToFit(IGeoPoint geoCenterPoint, Object... dbgContext) { QueryParameter baseQuery = getQueryForPositionRectangle(geoCenterPoint); BoundingBox boundingBox = null; - IGeoRectangle fittingRectangle = FotoSql.execGetGeoRectangle(null, this.getActivity(), + IGeoRectangle fittingRectangle = FotoSql.execGetGeoRectangle(null, baseQuery, null, mDebugPrefix, "zoomToFit", dbgContext); double delta = getDelta(fittingRectangle); if ((geoCenterPoint != null) && (delta < 1e-6)) { @@ -1194,7 +1194,7 @@ private double getMarkerDelta() { private IGeoPoint getGeoPointById(int markerId, IGeoPoint notFoundValue, Object... dbgContext) { if (markerId != NO_MARKER_ID) { - IGeoPoint pos = FotoSql.execGetPosition(null, this.getActivity(), + IGeoPoint pos = FotoSql.execGetPosition(null, null, markerId, mDebugPrefix, "getGeoPointById", dbgContext); if (pos != null) { return pos; diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/MapGeoPickerActivity.java b/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/MapGeoPickerActivity.java index 72c81766..4e3d7d0a 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/MapGeoPickerActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/MapGeoPickerActivity.java @@ -94,7 +94,7 @@ public static void showActivity(String debugContext, Activity context, SelectedF } if (AffUtils.putSelectedFiles(intent, selectedItems)) { - IGeoPoint initialPoint = FotoSql.execGetPosition(null, context, + IGeoPoint initialPoint = FotoSql.execGetPosition(null, null, selectedItems.getId(0), context, mDebugPrefix, "showActivity"); if (initialPoint != null) { GeoUri PARSER = new GeoUri(GeoUri.OPT_PARSE_INFER_MISSING); 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 8dae8276..7ba13187 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 @@ -33,7 +33,6 @@ import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.R; -import de.k3b.android.androFotoFinder.queries.ContentProviderMediaExecuter; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.osmdroid.ClickableIconOverlay; import de.k3b.android.osmdroid.IconFactory; @@ -96,8 +95,8 @@ protected OverlayManager doInBackground(QueryParameter... queryParameter) { Cursor cursor = null; try { - cursor = ContentProviderMediaExecuter.createCursorForQuery( - null, "MakerLoader", mContext, + cursor = FotoSql.getMediaDBApi().createCursorForQuery( + null, "MakerLoader", queryParameters, null); int itemCount = cursor.getCount(); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/PickerLocationMapFragment.java b/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/PickerLocationMapFragment.java index fbb6c292..53b4bb73 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/PickerLocationMapFragment.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/PickerLocationMapFragment.java @@ -170,7 +170,7 @@ protected void onOk() { Activity activity = getActivity(); if (mMarkerId != NO_MARKER_ID) { - result = FotoSql.execGetPosition(null, activity, null, mMarkerId, + result = FotoSql.execGetPosition(null, null, mMarkerId, mDebugPrefix,"onOk"); } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/media/PhotoPropertiesMediaDBCsvImportActivity.java b/app/src/main/java/de/k3b/android/androFotoFinder/media/PhotoPropertiesMediaDBCsvImportActivity.java index ed4ecff4..4fd1bf49 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/media/PhotoPropertiesMediaDBCsvImportActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/media/PhotoPropertiesMediaDBCsvImportActivity.java @@ -19,12 +19,12 @@ package de.k3b.android.androFotoFinder.media; +import android.app.Activity; import android.content.ContentValues; import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; -import android.app.Activity; import android.util.Log; import android.view.View; import android.widget.TextView; @@ -206,7 +206,7 @@ private void updateDB(String dbgContext, String _path, long xmlLastFileModifyDat TagSql.setFileModifyDate(dbValues, new Date().getTime() / 1000); - mUpdateCount += TagSql.execUpdate(dbgContext, PhotoPropertiesMediaDBCsvImportActivity.this, path, xmlLastFileModifyDate, dbValues, VISIBILITY.PRIVATE_PUBLIC); + mUpdateCount += TagSql.execUpdate(dbgContext, path, xmlLastFileModifyDate, dbValues, VISIBILITY.PRIVATE_PUBLIC); mItemCount++; } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/AndroidAlbumUtils.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/AndroidAlbumUtils.java index 9bceb428..56f8b5c8 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/AndroidAlbumUtils.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/AndroidAlbumUtils.java @@ -155,7 +155,7 @@ public static QueryParameter getQueryFromUriOrNull( QueryParameter query = QueryParameter.load(context.getContentResolver().openInputStream(uri)); if (query != null) { - Map found = FotoSql.execGetPathIdMap(context, path); + Map found = FotoSql.execGetPathIdMap(path); if ((found == null) || (found.size() == 0)) { AndroidAlbumUtils.albumMediaScan(dbgContext + " not found mediadb => ", context, new File(path), 1); } @@ -368,7 +368,7 @@ public static void insertToMediaDB(String dbgContext, @NonNull Context context, ContentValues values = new ContentValues(); String newAbsolutePath = PhotoPropertiesMediaFilesScanner.setFileFields(values, fileToBeScannedAndInserted); values.put(FotoSql.SQL_COL_EXT_MEDIA_TYPE, FotoSql.MEDIA_TYPE_ALBUM_FILE); - ContentProviderMediaExecuter.insertOrUpdateMediaDatabase(dbgContext, context, newAbsolutePath, values, null, 1l); + FotoSql.getMediaDBApi().insertOrUpdateMediaDatabase(dbgContext, newAbsolutePath, values, null, 1l); } } @@ -381,7 +381,7 @@ public static int albumMediaScan(String dbgContext, Context context, File _root, int result = 0; if (root != null) { List currentFiles = AlbumFile.getFilePaths(null, root, subDirLevels); - List databaseFiles = FotoSql.getAlbumFiles(context, FileUtils.tryGetCanonicalPath(root, null), subDirLevels); + List databaseFiles = FotoSql.getAlbumFiles(FileUtils.tryGetCanonicalPath(root, null), subDirLevels); List added = new ArrayList(); List removed = new ArrayList(); @@ -392,7 +392,7 @@ public static int albumMediaScan(String dbgContext, Context context, File _root, result++; } - result += FotoSql.deleteMedia(dbgMessage + "delete-obsolete", context, removed, false); + result += FotoSql.deleteMedia(dbgMessage + "delete-obsolete", removed, false); } return result; } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/ContentProviderMediaExecuter.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/ContentProviderMediaImpl.java similarity index 98% rename from app/src/main/java/de/k3b/android/androFotoFinder/queries/ContentProviderMediaExecuter.java rename to app/src/main/java/de/k3b/android/androFotoFinder/queries/ContentProviderMediaImpl.java index 7eee65be..45e6f3f7 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/ContentProviderMediaExecuter.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/ContentProviderMediaImpl.java @@ -35,7 +35,10 @@ import de.k3b.io.StringUtils; import de.k3b.io.VISIBILITY; -public class ContentProviderMediaExecuter { +/** + * Static Implementation of Context.getContentResolver()-ContentProvider based media api + */ +public class ContentProviderMediaImpl { public static Cursor createCursorForQuery( StringBuilder out_debugMessage, String dbgContext, final Context context, QueryParameter parameters, VISIBILITY visibility) { 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 a725ba0f..6196bc98 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 @@ -19,7 +19,6 @@ package de.k3b.android.androFotoFinder.queries; -import android.app.Activity; import android.content.ContentValues; import android.content.Context; import android.content.CursorLoader; @@ -280,6 +279,16 @@ public class FotoSql extends FotoSqlBase { */ private static final int SIZE_TRANLATION_LIMIT = SIZE_K * 10; + private static IMediaDBApi mediaDBApi; + + public static IMediaDBApi getMediaDBApi() { + return FotoSql.mediaDBApi; + } + + public static void setMediaDBApi(IMediaDBApi mediaDBApi) { + FotoSql.mediaDBApi = mediaDBApi; + } + public static final double getGroupFactor(final double _zoomLevel) { double zoomLevel = _zoomLevel; double result = GROUPFACTOR_FOR_Z0; @@ -755,10 +764,16 @@ public static boolean set(GalleryFilterParameter dest, String selectedAbsolutePa } /** converts content-Uri-with-id to full path */ - public static String execGetFotoPath(Context context, Uri uriWithID) { + public static String execGetFotoPath(Uri uriWithID) { Cursor c = null; try { - c = ContentProviderMediaExecuter.createCursorForQuery(null, "execGetFotoPath(uri)", context, uriWithID.toString(), null, null, null, FotoSql.SQL_COL_PATH); + c = mediaDBApi.createCursorForQuery( + null, + "execGetFotoPath(uri)", + uriWithID.toString(), + null, + null, null, + FotoSql.SQL_COL_PATH); if (c.moveToFirst()) { return DBUtils.getString(c,FotoSql.SQL_COL_PATH, null); } @@ -771,14 +786,20 @@ public static String execGetFotoPath(Context context, Uri uriWithID) { } /** search for all full-image-file-paths that matches pathfilter */ - public static List execGetFotoPaths(Context context, String pathFilter) { + public static List execGetFotoPaths(String pathFilter) { ArrayList result = new ArrayList(); Cursor c = null; try { - c = ContentProviderMediaExecuter.createCursorForQuery(null, "execGetFotoPaths(pathFilter)", context, SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, - FotoSql.SQL_COL_PATH + " like ? and " + FILTER_EXPR_PRIVATE_PUBLIC, - new String[]{pathFilter}, FotoSql.SQL_COL_PATH, FotoSql.SQL_COL_PATH); + QueryParameter query = new QueryParameter() + .addFrom(SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME) + .addWhere(FotoSql.SQL_COL_PATH + " like ? and " + FILTER_EXPR_PRIVATE_PUBLIC, pathFilter) + .addColumn(FotoSql.SQL_COL_PATH) + .addOrderBy(FotoSql.SQL_COL_PATH); + c = mediaDBApi.createCursorForQuery( + null, + "execGetFotoPaths(pathFilter)", + query, null); while (c.moveToNext()) { result.add(c.getString(0)); } @@ -794,7 +815,7 @@ public static List execGetFotoPaths(Context context, String pathFilter) return result; } - public static IGeoRectangle execGetGeoRectangle(StringBuilder out_debugMessage, Context context, QueryParameter baseQuery, + public static IGeoRectangle execGetGeoRectangle(StringBuilder out_debugMessage, QueryParameter baseQuery, SelectedItems selectedItems, Object... dbgContext) { StringBuilder debugMessage = (out_debugMessage == null) ? StringUtils.createDebugMessage(Global.debugEnabledSql, dbgContext) @@ -823,7 +844,7 @@ public static IGeoRectangle execGetGeoRectangle(StringBuilder out_debugMessage, GeoRectangle result = null; Cursor c = null; try { - c = ContentProviderMediaExecuter.createCursorForQuery(debugMessage, "execGetGeoRectangle", context, query, VISIBILITY.PRIVATE_PUBLIC); + c = mediaDBApi.createCursorForQuery(debugMessage, "execGetGeoRectangle", query, VISIBILITY.PRIVATE_PUBLIC); if (c.moveToFirst()) { result = new GeoRectangle(); result.setLatitude(c.getDouble(0), c.getDouble(1)); @@ -846,7 +867,7 @@ public static IGeoRectangle execGetGeoRectangle(StringBuilder out_debugMessage, } /** gets IGeoPoint either from file if fullPath is not null else from db via id */ - public static IGeoPoint execGetPosition(StringBuilder out_debugMessage, Context context, + public static IGeoPoint execGetPosition(StringBuilder out_debugMessage, String fullPath, long id, Object... dbgContext) { StringBuilder debugMessage = (out_debugMessage == null) ? StringUtils.createDebugMessage(Global.debugEnabledSql, dbgContext) : out_debugMessage; QueryParameter query = new QueryParameter() @@ -867,7 +888,7 @@ public static IGeoPoint execGetPosition(StringBuilder out_debugMessage, Context GeoPoint result = null; Cursor c = null; try { - c = ContentProviderMediaExecuter.createCursorForQuery(debugMessage, "execGetPosition", context, query, VISIBILITY.PRIVATE_PUBLIC); + c = mediaDBApi.createCursorForQuery(debugMessage, "execGetPosition", query, VISIBILITY.PRIVATE_PUBLIC); if (c.moveToFirst()) { result = new GeoPoint(c.getDouble(0),c.getDouble(1)); return result; @@ -889,7 +910,7 @@ public static IGeoPoint execGetPosition(StringBuilder out_debugMessage, Context /** * @return returns a hashmap filename => mediaID */ - public static Map execGetPathIdMap(Context context, String... fileNames) { + public static Map execGetPathIdMap(String... fileNames) { Map result = new HashMap(); String whereFileNames = getWhereInFileNames(fileNames); @@ -902,7 +923,7 @@ public static Map execGetPathIdMap(Context context, String... file Cursor c = null; try { - c = ContentProviderMediaExecuter.createCursorForQuery(null, "execGetPathIdMap", context, query, null); + c = mediaDBApi.createCursorForQuery(null, "execGetPathIdMap", query, null); while (c.moveToNext()) { result.put(c.getString(1),c.getLong(0)); } @@ -941,7 +962,7 @@ public static String getWhereInFileNames(String... fileNames) { * "/storage/sdcard0/testFolder/image.jpg" becomes "/storage/sdcard0/renamedFolder/image.jpg" * @return number of updated items */ - public static int execRenameFolder(Context context, String pathOld, String pathNew) { + public static int execRenameFolder(String pathOld, String pathNew) { final String dbgContext = "FotoSql.execRenameFolder('" + pathOld + "' => '" + pathNew + "')"; // sql update file set path = newBegin + substing(path, begin+len) where path like newBegin+'%' @@ -961,7 +982,7 @@ public static int execRenameFolder(Context context, String pathOld, String pathN .addWhere(SQL_COL_EXT_MEDIA_TYPE + " IS NOT NULL") ; - SelectedFiles selectedFiles= getSelectedfiles(context, queryAffectedFiles, sqlColNewPathAlias, null); + SelectedFiles selectedFiles = getSelectedfiles(queryAffectedFiles, sqlColNewPathAlias, null); String[] paths = selectedFiles.getFileNames(); Long[] ids = selectedFiles.getIds(); @@ -972,7 +993,7 @@ public static int execRenameFolder(Context context, String pathOld, String pathN for (int i = 0; i < ids.length; i++) { values.put(SQL_COL_PATH, paths[i]); selectionArgs[0] = ids[i].toString(); - if (ContentProviderMediaExecuter.exexUpdateImpl(_dbgContext, context, values, FILTER_COL_PK, selectionArgs) < 0) + if (mediaDBApi.exexUpdateImpl(_dbgContext, values, FILTER_COL_PK, selectionArgs) < 0) return -1; _dbgContext = null; } @@ -995,21 +1016,21 @@ public static CursorLoader createCursorLoader(Context context, final QueryParame return loader; } - public static int execDeleteByPath(String dbgContext, Activity context, String parentDirString, VISIBILITY visibility) { - int delCount = ContentProviderMediaExecuter.deleteMedia(dbgContext, context, getFilterExprPathLikeWithVisibility(visibility), new String[]{parentDirString + "/%"}, true); + public static int execDeleteByPath(String dbgContext, String parentDirString, VISIBILITY visibility) { + int delCount = mediaDBApi.deleteMedia(dbgContext, getFilterExprPathLikeWithVisibility(visibility), new String[]{parentDirString + "/%"}, true); return delCount; } - public static int deleteMedia(String dbgContext, Context context, List pathsToBeRemoved, + public static int deleteMedia(String dbgContext, List pathsToBeRemoved, boolean preventDeleteImageFile) { if ((pathsToBeRemoved != null) && (pathsToBeRemoved.size() > 0)) { String whereDelete = SQL_COL_PATH + " in ('" + ListUtils.toString("','", pathsToBeRemoved) + "')"; - return ContentProviderMediaExecuter.deleteMedia(dbgContext, context, whereDelete, null, preventDeleteImageFile); + return mediaDBApi.deleteMedia(dbgContext, whereDelete, null, preventDeleteImageFile); } return 0; } - public static int deleteMediaWithNullPath(Context context) { + public static int deleteMediaWithNullPath() { /// delete where SQL_COL_PATH + " is null" throws null pointer exception QueryParameter wherePathIsNull = new QueryParameter(); wherePathIsNull.addWhere(SQL_COL_PATH + " is null"); @@ -1017,13 +1038,13 @@ public static int deleteMediaWithNullPath(Context context) { // return deleteMedia("delete without path (_data = null)", context, wherePathIsNull.toAndroidWhere(), null, false); - SelectedFiles filesWitoutPath = getSelectedfiles(context, wherePathIsNull, FotoSql.SQL_COL_PATH, VISIBILITY.PRIVATE_PUBLIC); + SelectedFiles filesWitoutPath = getSelectedfiles(wherePathIsNull, FotoSql.SQL_COL_PATH, VISIBILITY.PRIVATE_PUBLIC); String pksAsString = filesWitoutPath.toIdString(); if ((pksAsString != null) && (pksAsString.length() > 0)) { QueryParameter whereInIds = new QueryParameter(); FotoSql.setWhereSelectionPks(whereInIds, pksAsString); - return ContentProviderMediaExecuter.deleteMedia("delete without path (_data = null)", context, whereInIds.toAndroidWhere(), null, true); + return mediaDBApi.deleteMedia("delete without path (_data = null)", whereInIds.toAndroidWhere(), null, true); } return 0; } @@ -1058,17 +1079,17 @@ public static String getUriString(long imageID) { return SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME + "/" + imageID; } - public static SelectedFiles getSelectedfiles(Context context, String sqlWhere, VISIBILITY visibility) { + public static SelectedFiles getSelectedfiles(String sqlWhere, VISIBILITY visibility) { QueryParameter query = new QueryParameter(FotoSql.queryChangePath); query.addWhere(sqlWhere); query.addOrderBy(FotoSql.SQL_COL_PATH); - return getSelectedfiles(context, query, FotoSql.SQL_COL_PATH, visibility); + return getSelectedfiles(query, FotoSql.SQL_COL_PATH, visibility); } @Nullable - public static String getMinFolder(Context context, QueryParameter query, + public static String getMinFolder(QueryParameter query, boolean removeLastModifiedFromFilter) { QueryParameter queryModified = new QueryParameter(query); queryModified.clearColumns().addColumn("min(" + SQL_EXPR_FOLDER + ")"); @@ -1081,7 +1102,7 @@ public static String getMinFolder(Context context, QueryParameter query, Cursor c = null; try { - c = ContentProviderMediaExecuter.createCursorForQuery(null, "getCount", context, queryModified, null); + c = mediaDBApi.createCursorForQuery(null, "getCount", queryModified, null); if (c.moveToNext()) { return c.getString(0); } @@ -1094,13 +1115,13 @@ public static String getMinFolder(Context context, QueryParameter query, } @Nullable - public static long getCount(Context context, QueryParameter query) { + public static long getCount(QueryParameter query) { QueryParameter queryModified = new QueryParameter(query); queryModified.clearColumns().addColumn("count(*)"); Cursor c = null; try { - c = ContentProviderMediaExecuter.createCursorForQuery(null, "getCount", context, queryModified, null); + c = mediaDBApi.createCursorForQuery(null, "getCount", queryModified, null); if (c.moveToNext()) { return c.getLong(0); } @@ -1129,7 +1150,7 @@ public static CharSequence getStatisticsMessage(Context context, int prefixStrin Cursor c = null; try { - c = ContentProviderMediaExecuter.createCursorForQuery(null, "getCount", context, queryModified, null); + c = mediaDBApi.createCursorForQuery(null, "getCount", queryModified, null); if (c.moveToNext()) { final long count = c.getLong(0); long size = c.getLong(1); @@ -1160,12 +1181,12 @@ public static CharSequence getStatisticsMessage(Context context, int prefixStrin } @Nullable - private static SelectedFiles getSelectedfiles(Context context, QueryParameter query, String colnameForPath, VISIBILITY visibility) { + private static SelectedFiles getSelectedfiles(QueryParameter query, String colnameForPath, VISIBILITY visibility) { SelectedFiles result = null; Cursor c = null; try { - c = ContentProviderMediaExecuter.createCursorForQuery(null, "getSelectedfiles", context, query, visibility); + c = mediaDBApi.createCursorForQuery(null, "getSelectedfiles", query, visibility); int len = c.getCount(); Long[] ids = new Long[len]; String[] paths = new String[len]; @@ -1199,13 +1220,13 @@ public static Date getDate(Cursor cursor,int colDateTimeTaken) { } /** converts internal ID-list to string array of filenNames via media database. */ - public static List getFileNames(Context context, SelectedItems items, List ids, List paths, List datesPhotoTaken) { + public static List getFileNames(SelectedItems items, List ids, List paths, List datesPhotoTaken) { if (!items.isEmpty()) { // query ordered by DatePhotoTaken so that lower rename-numbers correspond to older images. QueryParameter parameters = new QueryParameter(queryAutoRename); setWhereSelectionPks(parameters, items); - List result = getFileNamesImpl(context, parameters, ids, paths, datesPhotoTaken); + List result = getFileNamesImpl(parameters, ids, paths, datesPhotoTaken); int size = result.size(); if (size > 0) { @@ -1217,13 +1238,13 @@ public static List getFileNames(Context context, SelectedItems items, Li } /** converts internal ID-list to string array of filenNames via media database. */ - public static String[] getFileNames(Context context, String pksAsListString , List ids, List paths, List datesPhotoTaken) { + public static String[] getFileNames(String pksAsListString, List ids, List paths, List datesPhotoTaken) { if ((pksAsListString != null) && !pksAsListString.isEmpty()) { // query ordered by DatePhotoTaken so that lower rename-numbers correspond to older images. QueryParameter parameters = new QueryParameter(queryAutoRename); setWhereSelectionPks(parameters, pksAsListString); - List result = getFileNamesImpl(context, parameters, ids, paths, datesPhotoTaken); + List result = getFileNamesImpl(parameters, ids, paths, datesPhotoTaken); int size = result.size(); if (size > 0) { @@ -1233,13 +1254,13 @@ public static String[] getFileNames(Context context, String pksAsListString , Li return null; } - private static List getFileNamesImpl(Context context, QueryParameter parameters, List ids, List paths, List datesPhotoTaken) { + private static List getFileNamesImpl(QueryParameter parameters, List ids, List paths, List datesPhotoTaken) { List result = (paths != null) ? paths : new ArrayList(); Cursor cursor = null; try { - cursor = ContentProviderMediaExecuter.createCursorForQuery(null, "getFileNames", context, parameters, VISIBILITY.PRIVATE_PUBLIC); + cursor = mediaDBApi.createCursorForQuery(null, "getFileNames", parameters, VISIBILITY.PRIVATE_PUBLIC); int colPath = cursor.getColumnIndex(SQL_COL_DISPLAY_TEXT); if (colPath == -1) colPath = cursor.getColumnIndex(SQL_COL_PATH); @@ -1294,8 +1315,8 @@ public static QueryParameter setWhereVisibility(QueryParameter parameters, VISIB return parameters; } - public static List getAlbumFiles(Context context, String path, int subDirLevels) { - SelectedFiles databaseFiles = FotoSql.getSelectedfiles(context, + public static List getAlbumFiles(String path, int subDirLevels) { + SelectedFiles databaseFiles = FotoSql.getSelectedfiles( SQL_COL_PATH +" like '" + path + "/%" + AlbumFile.SUFFIX_VALBUM + "' OR " + SQL_COL_PATH +" like '" + path + "/%" + AlbumFile.SUFFIX_QUERY + "'", null); String[] fileNames = (databaseFiles == null) ? null : databaseFiles.getFileNames(); @@ -1314,10 +1335,10 @@ public static List getAlbumFiles(Context context, String path, int subDi return paths; } - public static int execRename(Context context, String oldFullPath, String newFullPath) { + public static int execRename(String oldFullPath, String newFullPath) { ContentValues values = new ContentValues(); values.put(SQL_COL_PATH, newFullPath); - return ContentProviderMediaExecuter.execUpdate("rename file", context, oldFullPath, + return mediaDBApi.execUpdate("rename file", oldFullPath, values, null); } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/FotoThumbSql.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/FotoThumbSql.java index b9b00c78..bbf0661c 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/FotoThumbSql.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/FotoThumbSql.java @@ -66,7 +66,7 @@ private static String getStatistic(Context context, QueryParameter query, String Cursor c = null; try { - c = ContentProviderMediaExecuter.createCursorForQuery(null, mDebugPrefix + "getStatistic", context, query, VISIBILITY.PRIVATE_PUBLIC); + c = FotoSql.getMediaDBApi().createCursorForQuery(null, mDebugPrefix + "getStatistic", query, VISIBILITY.PRIVATE_PUBLIC); if (Global.debugEnabledSql) { Log.i(Global.LOG_CONTEXT, mDebugPrefix + "getStatistic " + c.getCount() + "\n\t" + query.toSqlString()); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/IMediaDBApi.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/IMediaDBApi.java new file mode 100644 index 00000000..87560b3b --- /dev/null +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/IMediaDBApi.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see + */ +package de.k3b.android.androFotoFinder.queries; + +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; + +import de.k3b.database.QueryParameter; +import de.k3b.io.VISIBILITY; + +/** + * media database api + */ +public interface IMediaDBApi { + Cursor createCursorForQuery( + StringBuilder out_debugMessage, String dbgContext, + QueryParameter parameters, VISIBILITY visibility); + + Cursor createCursorForQuery(StringBuilder out_debugMessage, String dbgContext, final String from, final String sqlWhereStatement, + final String[] sqlWhereParameters, final String sqlSortOrder, + final String... sqlSelectColums); + + int execUpdate(String dbgContext, long id, ContentValues values); + + int execUpdate(String dbgContext, String path, ContentValues values, VISIBILITY visibility); + + int exexUpdateImpl(String dbgContext, ContentValues values, String sqlWhere, String[] selectionArgs); + + /** + * return id of inserted item + */ + Long insertOrUpdateMediaDatabase(String dbgContext, + String dbUpdateFilterJpgFullPathName, + ContentValues values, VISIBILITY visibility, + Long updateSuccessValue); + + /** + * every database insert should go through this. adds logging if enabled + */ + Uri execInsert(String dbgContext, ContentValues values); + + /** + * Deletes media items specified by where with the option to prevent cascade delete of the image. + */ + int deleteMedia(String dbgContext, String where, String[] selectionArgs, boolean preventDeleteImageFile); + + ContentValues getDbContent(long 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 new file mode 100644 index 00000000..645a6078 --- /dev/null +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBContentprovider.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2019 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see + */ +package de.k3b.android.androFotoFinder.queries; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; + +import de.k3b.database.QueryParameter; +import de.k3b.io.VISIBILITY; + +/** + * Implementation of Context.getContentResolver()-ContentProvider based media api + */ +public class MediaDBContentprovider implements IMediaDBApi { + private final Context context; + + public MediaDBContentprovider(final Context context) { + this.context = context; + } + + @Override + public Cursor createCursorForQuery( + StringBuilder out_debugMessage, String dbgContext, + QueryParameter parameters, VISIBILITY visibility) { + return ContentProviderMediaImpl.createCursorForQuery( + out_debugMessage, dbgContext, context, parameters, visibility); + } + + @Override + public Cursor createCursorForQuery(StringBuilder out_debugMessage, String dbgContext, final String from, final String sqlWhereStatement, + final String[] sqlWhereParameters, final String sqlSortOrder, + final String... sqlSelectColums) { + return ContentProviderMediaImpl.createCursorForQuery( + out_debugMessage, dbgContext, context, from, sqlWhereStatement, + sqlWhereParameters, sqlSortOrder, sqlSelectColums); + } + + @Override + public int execUpdate(String dbgContext, long id, ContentValues values) { + return ContentProviderMediaImpl.execUpdate(dbgContext, context, id, values); + } + + @Override + public int execUpdate(String dbgContext, String path, ContentValues values, VISIBILITY visibility) { + return ContentProviderMediaImpl.execUpdate(dbgContext, context, path, values, visibility); + } + + @Override + public int exexUpdateImpl(String dbgContext, ContentValues values, String sqlWhere, String[] selectionArgs) { + return ContentProviderMediaImpl.exexUpdateImpl(dbgContext, context, values, sqlWhere, selectionArgs); + } + + /** + * return id of inserted item + */ + @Override + public Long insertOrUpdateMediaDatabase(String dbgContext, + String dbUpdateFilterJpgFullPathName, + ContentValues values, VISIBILITY visibility, + Long updateSuccessValue) { + return ContentProviderMediaImpl.insertOrUpdateMediaDatabase(dbgContext, context, + dbUpdateFilterJpgFullPathName, + values, visibility, + updateSuccessValue); + } + + /** + * every database insert should go through this. adds logging if enabled + */ + @Override + public Uri execInsert(String dbgContext, ContentValues values) { + return ContentProviderMediaImpl.execInsert(dbgContext, context, 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 ContentProviderMediaImpl.deleteMedia(dbgContext, context, where, selectionArgs, preventDeleteImageFile); + } + + @Override + public ContentValues getDbContent(final long id) { + return ContentProviderMediaImpl.getDbContent(context, id); + } +} 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 4818a043..c059ae32 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 @@ -187,7 +187,7 @@ public static int updateMedaiCopy(Context context, SQLiteDatabase db, Date lastU Cursor c = null; ContentValues contentValues = new ContentValues(); try { - c = ContentProviderMediaExecuter.createCursorForQuery(null, "execGetFotoPaths(pathFilter)", context, + c = ContentProviderMediaImpl.createCursorForQuery(null, "execGetFotoPaths(pathFilter)", context, query, null); while (c.moveToNext()) { getContentValues(c, contentValues); 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 4a580e90..515915d5 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 @@ -65,8 +65,8 @@ protected SelectedItems doInBackground(QueryParameter... querys) { Cursor cursor = null; try { - cursor = ContentProviderMediaExecuter.createCursorForQuery( - null, "SqlJobTask", mContext, + cursor = FotoSql.getMediaDBApi().createCursorForQuery( + null, "SqlJobTask", query, null); int itemCount = cursor.getCount(); 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 a9d7c4db..169329f3 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 @@ -20,7 +20,6 @@ package de.k3b.android.androFotoFinder.tagDB; import android.content.ContentValues; -import android.content.Context; import android.database.Cursor; import android.provider.MediaStore; import android.util.Log; @@ -34,7 +33,6 @@ import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.media.PhotoPropertiesMediaDBContentValues; import de.k3b.android.androFotoFinder.queries.AndroidAlbumUtils; -import de.k3b.android.androFotoFinder.queries.ContentProviderMediaExecuter; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.util.PhotoPropertiesMediaFilesScanner; import de.k3b.database.QueryParameter; @@ -259,7 +257,7 @@ public static void addWhereTagsIncluded(QueryParameter resultQuery, List } } - public static int fixPrivate(Context context) { + public static int fixPrivate() { // update ... set media_type=1001 where media_type=1 and tags like '%;PRIVATE;%' ContentValues values = new ContentValues(); values.put(SQL_COL_EXT_MEDIA_TYPE, MEDIA_TYPE_IMAGE_PRIVATE); @@ -273,7 +271,7 @@ public static int fixPrivate(Context context) { PhotoPropertiesUtil.IMG_TYPE_PRIVATE + "'")); } where.append(")"); - return ContentProviderMediaExecuter.exexUpdateImpl("Fix visibility private", context, + return getMediaDBApi().exexUpdateImpl("Fix visibility private", values, where.toString(), new String[] {"%;" + VISIBILITY.TAG_PRIVATE + ";%"}); } @@ -332,7 +330,7 @@ public static void setFileModifyDate(ContentValues values, long fileModifyDateSe * @param allowSetNulls if one of these columns are null, the set null is copied, too * @return number of changed db items */ - public static int updateDB(String dbgContext, Context context, String oldFullJpgFilePath, + public static int updateDB(String dbgContext, String oldFullJpgFilePath, PhotoPropertiesUpdateHandler jpg, MediaFormatter.FieldID... allowSetNulls) { if ((jpg != null) && (!PhotoPropertiesMediaFilesScanner.isNoMedia(oldFullJpgFilePath))) { ContentValues dbValues = new ContentValues(); @@ -358,7 +356,7 @@ public static int updateDB(String dbgContext, Context context, String oldFullJpg TagSql.setXmpFileModifyDate(dbValues, xmpFilelastModified); TagSql.setFileModifyDate(dbValues, newFullJpgFilePath); - return TagSql.execUpdate(dbgContext, context, oldFullJpgFilePath, + return TagSql.execUpdate(dbgContext, oldFullJpgFilePath, TagSql.EXT_LAST_EXT_SCAN_UNKNOWN, dbValues, VISIBILITY.PRIVATE_PUBLIC); } @@ -368,21 +366,21 @@ public static int updateDB(String dbgContext, Context context, String oldFullJpg } - public static int execUpdate(String dbgContext, Context context, String path, long xmpFileDate, ContentValues values, VISIBILITY visibility) { + public static int execUpdate(String dbgContext, String path, long xmpFileDate, ContentValues values, VISIBILITY visibility) { if ((!Global.Media.enableXmpNone) || (xmpFileDate == EXT_LAST_EXT_SCAN_UNKNOWN)) { - return ContentProviderMediaExecuter.execUpdate(dbgContext, context, path, values, visibility); + return getMediaDBApi().execUpdate(dbgContext, path, values, visibility); } - return ContentProviderMediaExecuter.exexUpdateImpl(dbgContext, context, values, FILTER_EXPR_PATH_LIKE_XMP_DATE, new String[]{path, Long.toString(xmpFileDate)}); + return getMediaDBApi().exexUpdateImpl(dbgContext, values, FILTER_EXPR_PATH_LIKE_XMP_DATE, new String[]{path, Long.toString(xmpFileDate)}); } /** return how many photos exist that have one or more tags from list */ - public static int getTagRefCount(Context context, List tags) { + public static int getTagRefCount(List tags) { QueryParameter query = new QueryParameter() .addColumn("count(*)").addFrom(SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME); if (addWhereAnyOfTags(query, tags) > 0) { Cursor c = null; try { - c = ContentProviderMediaExecuter.createCursorForQuery(null, "getTagRefCount", context, query, VISIBILITY.PRIVATE_PUBLIC); + c = getMediaDBApi().createCursorForQuery(null, "getTagRefCount", query, VISIBILITY.PRIVATE_PUBLIC); if (c.moveToFirst()) { return c.getInt(0); } @@ -412,12 +410,11 @@ public TagWorflowItem(long id, String path, List tags, long xmpLastModif /** * converts selectedItemPks and/or anyOfTags to TagWorflowItem-s - * @param context * @param selectedItemPks if not null list of comma seperated item-pks * @param anyOfTags if not null list of tag-s where at least one oft the tag must be in the photo. * @return */ - public static List loadTagWorflowItems(Context context, String selectedItemPks, List anyOfTags) { + public static List loadTagWorflowItems(String selectedItemPks, List anyOfTags) { QueryParameter query = new QueryParameter() .addColumn(TagSql.SQL_COL_PK, TagSql.SQL_COL_PATH, TagSql.SQL_COL_EXT_TAGS, TagSql.SQL_COL_EXT_XMP_LAST_MODIFIED_DATE); @@ -436,7 +433,7 @@ public static List loadTagWorflowItems(Context context, String s if (filterCount > 0) { try { - c = ContentProviderMediaExecuter.createCursorForQuery(null, "loadTagWorflowItems", context, query, VISIBILITY.PRIVATE_PUBLIC); + c = getMediaDBApi().createCursorForQuery(null, "loadTagWorflowItems", query, VISIBILITY.PRIVATE_PUBLIC); 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/androFotoFinder/tagDB/TagWorflow.java b/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagWorflow.java index 20962708..5e5bd9ea 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagWorflow.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagWorflow.java @@ -30,12 +30,12 @@ import de.k3b.android.androFotoFinder.Global; import de.k3b.android.util.AndroidFileCommands; +import de.k3b.io.FileCommands; import de.k3b.io.IProgessListener; import de.k3b.io.collections.SelectedFiles; -import de.k3b.io.FileCommands; import de.k3b.media.MediaFormatter; -import de.k3b.media.PhotoPropertiesXmpSegment; import de.k3b.media.PhotoPropertiesUpdateHandler; +import de.k3b.media.PhotoPropertiesXmpSegment; import de.k3b.tagDB.Tag; import de.k3b.tagDB.TagConverter; import de.k3b.tagDB.TagProcessor; @@ -124,7 +124,7 @@ protected int updateTags(TagSql.TagWorflowItem tagWorflowItemFromDB, List loadTagWorflowItems(Context context, String selectedItems, List anyOfTags) { - return TagSql.loadTagWorflowItems(context, selectedItems, anyOfTags); + return TagSql.loadTagWorflowItems(selectedItems, anyOfTags); } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagsPickerFragment.java b/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagsPickerFragment.java index a66f9cf6..262f7c33 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagsPickerFragment.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagsPickerFragment.java @@ -580,14 +580,14 @@ public boolean showTagDeleteDialog(final Tag item) { List rootList = new ArrayList(); rootList.add(item); - final int rootTagReferenceCount = TagSql.getTagRefCount(getActivity(), rootList); + final int rootTagReferenceCount = TagSql.getTagRefCount(rootList); List children = item.getChildren(loadTagRepositoryItems(false), true, false); if (children != null) rootList.addAll(children); final int allTagReferenceCount = (children == null) ? rootTagReferenceCount - : TagSql.getTagRefCount(getActivity(), rootList); + : TagSql.getTagRefCount(rootList); if (children == null) { chkDeleteChildren.setVisibility(View.GONE); @@ -690,7 +690,7 @@ protected View onCreateContentView(Activity parent) { List rootList = new ArrayList(); rootList.add(tag); - final int rootTagReferenceCount = TagSql.getTagRefCount(getActivity(), rootList); + final int rootTagReferenceCount = TagSql.getTagRefCount(rootList); chkUpdatePhotos.setText(getString(R.string.tags_update_photos) + " (" + rootTagReferenceCount + ")"); diff --git a/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java b/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java index 72f04c3a..5ac78acb 100644 --- a/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java +++ b/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java @@ -42,7 +42,6 @@ import de.k3b.android.androFotoFinder.R; import de.k3b.android.androFotoFinder.directory.DirectoryPickerFragment; import de.k3b.android.androFotoFinder.media.AndroidPhotoPropertiesBulkUpdateService; -import de.k3b.android.androFotoFinder.queries.ContentProviderMediaExecuter; import de.k3b.android.androFotoFinder.queries.DatabaseHelper; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.androFotoFinder.tagDB.TagSql; @@ -219,9 +218,9 @@ public int execRename(File srcDirFile, String newFolderName) { boolean isDir = srcDirFile.isDirectory(); if (srcDirFile.renameTo(destDirFile)) { if (isDir) { - modifyCount = FotoSql.execRenameFolder(this.mContext, srcDirFile.getAbsolutePath() + "/", destDirFile.getAbsolutePath() + "/"); + modifyCount = FotoSql.execRenameFolder(srcDirFile.getAbsolutePath() + "/", destDirFile.getAbsolutePath() + "/"); } else { - modifyCount = FotoSql.execRename(mContext, srcDirFile.getAbsolutePath(), destDirFile.getAbsolutePath()); + modifyCount = FotoSql.execRename(srcDirFile.getAbsolutePath(), destDirFile.getAbsolutePath()); } if (modifyCount < 0) { destDirFile.renameTo(srcDirFile); // error: undo change @@ -329,7 +328,7 @@ public int deleteFiles(SelectedFiles fotos, IProgessListener progessListener) { QueryParameter where = new QueryParameter(); FotoSql.setWhereSelectionPks (where, fotos.toIdString()); - ContentProviderMediaExecuter.deleteMedia("AndroidFileCommands.deleteFiles", mContext, where.toAndroidWhere(), null, true); + FotoSql.getMediaDBApi().deleteMedia("AndroidFileCommands.deleteFiles", where.toAndroidWhere(), null, true); } return deleteCount; } @@ -469,7 +468,7 @@ public int setGeo(double latitude, double longitude, SelectedFiles selectedItems } File file = files[i]; PhotoPropertiesUpdateHandler jpg = createWorkflow(null, dbgContext).saveLatLon(file, latitude, longitude); - resultFile += TagSql.updateDB(dbgContext, applicationContext, + resultFile += TagSql.updateDB(dbgContext, file.getAbsolutePath(), jpg, MediaFormatter.FieldID.latitude_longitude); itemcount++; addTransactionLog(selectedItems.getId(i), file.getAbsolutePath(), now, MediaTransactionLogEntryType.GPS, latLong); diff --git a/app/src/main/java/de/k3b/android/util/IntentUtil.java b/app/src/main/java/de/k3b/android/util/IntentUtil.java index 945a335a..9bf1ab41 100644 --- a/app/src/main/java/de/k3b/android/util/IntentUtil.java +++ b/app/src/main/java/de/k3b/android/util/IntentUtil.java @@ -65,7 +65,7 @@ public static File getExistingFileOrNull(Context context, Uri uri) { path = uri.getPath(); } else if ("content".equals(scheme)) { // try to translate via media db - path = FotoSql.execGetFotoPath(context, uri); + path = FotoSql.execGetFotoPath(uri); if (path != null) { if (Global.debugEnabled) { Log.i(Global.LOG_CONTEXT, "Translate from '" + uri + 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 2388e523..5d9ac8de 100644 --- a/app/src/main/java/de/k3b/android/util/PhotoPropertiesMediaFilesScanner.java +++ b/app/src/main/java/de/k3b/android/util/PhotoPropertiesMediaFilesScanner.java @@ -44,7 +44,6 @@ import de.k3b.LibGlobal; import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.media.PhotoPropertiesMediaDBContentValues; -import de.k3b.android.androFotoFinder.queries.ContentProviderMediaExecuter; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.androFotoFinder.tagDB.TagSql; import de.k3b.database.QueryParameter; @@ -150,7 +149,7 @@ public static int hideFolderMedia(Activity context, String path) { if (Global.debugEnabled) { Log.i(Global.LOG_CONTEXT, CONTEXT + " hideFolderMedia: delete from media db " + path + "/**"); } - result = FotoSql.execDeleteByPath(CONTEXT + " hideFolderMedia", context, path, VISIBILITY.PRIVATE_PUBLIC); + result = FotoSql.execDeleteByPath(CONTEXT + " hideFolderMedia", path, VISIBILITY.PRIVATE_PUBLIC); if (result > 0) { PhotoPropertiesMediaFilesScanner.notifyChanges(context, "hide " + path + "/**"); } @@ -171,7 +170,7 @@ public int updateMediaDatabase_Android42(Context context, String[] oldPathNames, } if (hasNew) { result = insertIntoMediaDatabase(context, newPathNames); } - TagSql.fixPrivate(context); + TagSql.fixPrivate(); return result; } @@ -208,7 +207,7 @@ private int insertIntoMediaDatabase(Context context, String[] newPathNames) { Log.i(Global.LOG_CONTEXT, CONTEXT + "A42 scanner starting with " + newPathNames.length + " files " + newPathNames[0] + "..."); } - Map inMediaDb = FotoSql.execGetPathIdMap(context.getApplicationContext(), newPathNames); + Map inMediaDb = FotoSql.execGetPathIdMap(newPathNames); for (String fileName : newPathNames) { if (fileName != null) { @@ -233,8 +232,8 @@ public Long insertOrUpdateMediaDatabase(String dbgContext, Context context, if ((currentJpgFile != null) && currentJpgFile.exists() && currentJpgFile.canRead()) { ContentValues values = createDefaultContentValues(); getExifFromFile(values, currentJpgFile); - Long result = ContentProviderMediaExecuter.insertOrUpdateMediaDatabase( - dbgContext, context, dbUpdateFilterJpgFullPathName, + Long result = FotoSql.getMediaDBApi().insertOrUpdateMediaDatabase( + dbgContext, dbUpdateFilterJpgFullPathName, values, VISIBILITY.PRIVATE_PUBLIC, updateSuccessValue); return result; @@ -249,7 +248,7 @@ private int deleteInMediaDatabase(Context context, String[] oldPathNames) { if ((oldPathNames != null) && (oldPathNames.length > 0)) { String sqlWhere = FotoSql.getWhereInFileNames(oldPathNames); try { - modifyCount = ContentProviderMediaExecuter.deleteMedia(CONTEXT + "deleteInMediaDatabase", context, sqlWhere, null, true); + modifyCount = FotoSql.getMediaDBApi().deleteMedia(CONTEXT + "deleteInMediaDatabase", sqlWhere, null, true); if (Global.debugEnabled) { Log.d(Global.LOG_CONTEXT, CONTEXT + "deleteInMediaDatabase(len=" + oldPathNames.length + ", files='" + oldPathNames[0] + "'...) result count=" + modifyCount); } @@ -301,7 +300,7 @@ private int renameInMediaDatabase(Context context, Map old2NewFi Cursor c = null; try { - c = ContentProviderMediaExecuter.createCursorForQuery(null, "renameInMediaDatabase", context, query, VISIBILITY.PRIVATE_PUBLIC); + c = FotoSql.getMediaDBApi().createCursorForQuery(null, "renameInMediaDatabase", query, VISIBILITY.PRIVATE_PUBLIC); int pkColNo = c.getColumnIndex(FotoSql.SQL_COL_PK); int pathColNo = c.getColumnIndex(FotoSql.SQL_COL_PATH); while (c.moveToNext()) { @@ -432,7 +431,7 @@ public int updatePathRelatedFields(Context context, Cursor cursor, String newAbs String oldAbsolutePath = cursor.getString(columnIndexPath); int id = cursor.getInt(columnIndexPk); setPathRelatedFieldsIfNeccessary(values, newAbsolutePath, oldAbsolutePath); - return ContentProviderMediaExecuter.execUpdate("updatePathRelatedFields", context, id, values); + return FotoSql.getMediaDBApi().execUpdate("updatePathRelatedFields", id, values); } /** sets the path related fields */ @@ -464,7 +463,7 @@ private int update_Android42(String dbgContext, Context context, long id, File f if ((file != null) && file.exists() && file.canRead()) { ContentValues values = createDefaultContentValues(); getExifFromFile(values, file); - return ContentProviderMediaExecuter.execUpdate(dbgContext, context, id, values); + return FotoSql.getMediaDBApi().execUpdate(dbgContext, id, values); } return 0; } @@ -488,7 +487,7 @@ private int insert_Android42(String dbgContext, Context context, File file) { FotoSql.addDateAdded(values); getExifFromFile(values, file); - return (null != ContentProviderMediaExecuter.execInsert(dbgContext, context, values)) ? 1 : 0; + return (null != FotoSql.getMediaDBApi().execInsert(dbgContext, values)) ? 1 : 0; } return 0; } 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 28/37] #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; From ea14a125888a0d399652787c96c33eb842a24b4e Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Wed, 18 Dec 2019 22:12:56 +0100 Subject: [PATCH 29/37] #155: load MediaImageDbReplacement from Contentprovider --- .../androFotoFinder/AndroFotoFinderApp.java | 43 ++++- .../androFotoFinder/FotoGalleryActivity.java | 10 +- .../k3b/android/androFotoFinder/Global.java | 3 +- .../androFotoFinder/SettingsActivity.java | 156 +++++++++--------- .../backup/BackupAsyncTask.java | 116 +------------ .../backup/BackupProgressActivity.java | 87 +++------- .../androFotoFinder/backup/package-info.java | 2 +- .../queries/DatabaseHelper.java | 20 +-- .../queries/MediaDBUpdater.java | 51 ++++++ .../queries/MediaImageDbReplacement.java | 144 +++++++++++++++- .../queries/MergedMediaDB.java | 127 ++++++++++++++ .../k3b/android/widget/ProgressActivity.java | 94 +++++++++++ .../android/widget/ProgressableAsyncTask.java | 152 +++++++++++++++++ app/src/main/res/menu/menu_ao10.xml | 10 ++ app/src/main/res/values/strings.xml | 3 + app/src/main/res/xml/preferences.xml | 6 + .../java/de/k3b/database/QueryParameter.java | 14 +- 17 files changed, 755 insertions(+), 283 deletions(-) create mode 100644 app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBUpdater.java create mode 100644 app/src/main/java/de/k3b/android/androFotoFinder/queries/MergedMediaDB.java create mode 100644 app/src/main/java/de/k3b/android/widget/ProgressActivity.java create mode 100644 app/src/main/java/de/k3b/android/widget/ProgressableAsyncTask.java create mode 100644 app/src/main/res/menu/menu_ao10.xml diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java index 65d3e901..8052fe7b 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java @@ -21,6 +21,7 @@ import android.app.Application; import android.content.Context; +import android.database.sqlite.SQLiteDatabase; import android.support.annotation.NonNull; import android.util.Log; import android.widget.Toast; @@ -36,9 +37,14 @@ import de.k3b.LibGlobal; import de.k3b.android.GuiUtil; import de.k3b.android.androFotoFinder.imagedetail.HugeImageLoader; +import de.k3b.android.androFotoFinder.queries.DatabaseHelper; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.androFotoFinder.queries.FotoSqlBase; +import de.k3b.android.androFotoFinder.queries.IMediaDBApi; import de.k3b.android.androFotoFinder.queries.MediaDBContentprovider; +import de.k3b.android.androFotoFinder.queries.MediaDBUpdater; +import de.k3b.android.androFotoFinder.queries.MediaImageDbReplacement; +import de.k3b.android.androFotoFinder.queries.MergedMediaDB; import de.k3b.android.osmdroid.forge.MapsForgeSupport; import de.k3b.android.util.LogCat; import de.k3b.android.widget.ActivityWithCallContext; @@ -58,6 +64,11 @@ */ public class AndroFotoFinderApp extends Application { private static String fileNamePrefix = "androFotofinder.logcat-"; + private static MediaDBUpdater mediaDbUpdater = null; + + public static MediaDBUpdater getMediaDbUpdater() { + return mediaDbUpdater; + } private LogCat mCrashSaveToFile = null; @@ -77,6 +88,35 @@ public static String getGetTeaserText(Context context, String linkUrlForDetails) return result; } + public static void setMediaImageDbReplacement(Context context, boolean useMediaImageDbReplacement) { + final IMediaDBApi oldMediaDBApi = FotoSql.getMediaDBApi(); + if ((oldMediaDBApi == null) || (Global.useMediaImageDbReplacement != useMediaImageDbReplacement)) { + Global.useMediaImageDbReplacement = useMediaImageDbReplacement; + + final MediaDBContentprovider mediaDBContentprovider = new MediaDBContentprovider(context); + + if (Global.useMediaImageDbReplacement) { + final SQLiteDatabase writableDatabase = DatabaseHelper.getWritableDatabase(context); + final MediaImageDbReplacement mediaImageDbReplacement = new MediaImageDbReplacement(writableDatabase); + FotoSql.setMediaDBApi(new MergedMediaDB(mediaImageDbReplacement, mediaDBContentprovider)); + + AndroFotoFinderApp.mediaDbUpdater = new MediaDBUpdater(context, writableDatabase); + + if (FotoSql.getCount(new QueryParameter().addWhere("1 = 1")) == 0) { + // database is empty; reload from Contentprovider + AndroFotoFinderApp.mediaDbUpdater.rebuild(context, null); + } + } else { + if ((oldMediaDBApi != null) && (AndroFotoFinderApp.mediaDbUpdater != null)) { + // switching from mediaImageDbReplacement to Contentprovider + AndroFotoFinderApp.mediaDbUpdater.clearMediaCopy(); + } + FotoSql.setMediaDBApi(mediaDBContentprovider); + AndroFotoFinderApp.mediaDbUpdater = null; + } + } + } + /* private RefWatcher refWatcher; @@ -95,9 +135,6 @@ public static RefWatcher getRefWatcher(Context context) { // StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyDeath().build()); FotoSqlBase.init(); - /// #155: todo: depending on android-api version set IMediaDBApi - FotoSql.setMediaDBApi(new MediaDBContentprovider(this)); - super.onCreate(); LibGlobal.appName = getString(R.string.app_name); 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 41ca82d3..883c4402 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/FotoGalleryActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/FotoGalleryActivity.java @@ -44,8 +44,8 @@ import de.k3b.android.widget.AboutDialogPreference; import de.k3b.android.widget.BaseQueryActivity; import de.k3b.database.QueryParameter; -import de.k3b.io.collections.SelectedItems; import de.k3b.io.IDirectory; +import de.k3b.io.collections.SelectedItems; /** * Gallery: Show zeoro or more images in a grid optionally filtered by a @@ -167,6 +167,11 @@ public boolean onCreateOptionsMenu(Menu menu) { inflater.inflate(R.menu.menu_gallery_non_selected_only, menu); inflater.inflate(R.menu.menu_gallery_non_multiselect, menu); + + if (Global.useMediaImageDbReplacement) { + inflater.inflate(R.menu.menu_ao10, menu); + } + /* getActionBar().setListNavigationCallbacks(); MenuItem sorter = menu.getItem(R.id.cmd_sort); @@ -202,6 +207,9 @@ public boolean onOptionsItemSelected(MenuItem item) { case R.id.cmd_about: AboutDialogPreference.createAboutDialog(this).show(); return true; + case R.id.cmd_db_reload: + AndroFotoFinderApp.getMediaDbUpdater().rebuild(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/Global.java b/app/src/main/java/de/k3b/android/androFotoFinder/Global.java index b654e478..21a12bb7 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/Global.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/Global.java @@ -127,7 +127,8 @@ public static class Media { // #155: fix android10 incompatibility // Build.VERSION_CODES.??ANDROID10?? = 29 //!!! - public static final boolean useMediaImageDbReplacement = true; + public static 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; diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java b/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java index fb04cffa..bdd79fa9 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java @@ -60,82 +60,6 @@ public class SettingsActivity extends PreferenceActivity { private int INSTALL_REQUEST_CODE = 1927; - @Override - protected void onCreate(final Bundle savedInstanceState) { - LocalizedActivity.fixLocale(this); // #21: Support to change locale at runtime - super.onCreate(savedInstanceState); - - if (Global.debugEnabled) { - // todo create junit integration tests with arabic locale from this. - StringFormatResourceTests.test(this); - } - - final Intent intent = getIntent(); - if (Global.debugEnabled && (intent != null)){ - Log.d(Global.LOG_CONTEXT, "SettingsActivity onCreate " + intent.toUri(Intent.URI_INTENT_SCHEME)); - } - - this.addPreferencesFromResource(R.xml.preferences); - prefsInstance = PreferenceManager - .getDefaultSharedPreferences(this); - global2Prefs(this.getApplication()); - - // #21: Support to change locale at runtime - defaultLocalePreference = - (ListPreference) findPreference(Global.PREF_KEY_USER_LOCALE); - defaultLocalePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - setLanguage((String) newValue); - LocalizedActivity.recreate(SettingsActivity.this); - return true; // change is allowed - } - }); - - mediaUpdateStrategyPreference = - (ListPreference) findPreference("mediaUpdateStrategy"); - mediaUpdateStrategyPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - LibGlobal.mediaUpdateStrategy = (String) newValue; - setPref(LibGlobal.mediaUpdateStrategy, mediaUpdateStrategyPreference, R.array.pref_media_update_strategy_names); - return true; - } - }); - setPref(LibGlobal.mediaUpdateStrategy, mediaUpdateStrategyPreference, R.array.pref_media_update_strategy_names); - - findPreference("debugClearLog").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - onDebugClearLogCat(); - return false; // donot close - } - }); - findPreference("debugSaveLog").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - onDebugSaveLogCat(); - return false; // donot close - } - }); - findPreference("translate").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - onTranslate(); - return false; // donot close - } - }); - - // #21: Support to change locale at runtime - updateSummary(); - } - - @Override - public void onPause() { - prefs2Global(this); - super.onPause(); - } - public static void global2Prefs(Context context) { fixDefaults(context, null, null); @@ -168,6 +92,7 @@ public static void global2Prefs(Context context) { prefs.putBoolean("xmp_file_schema_long", LibGlobal.preferLongXmpFormat); prefs.putBoolean("mapsForgeEnabled", Global.mapsForgeEnabled); + prefs.putBoolean("useMediaImageDbReplacement", Global.useMediaImageDbReplacement); prefs.putBoolean("locked", Global.locked); prefs.putString("passwordHash", Global.passwordHash); @@ -191,6 +116,12 @@ public static void global2Prefs(Context context) { } + @Override + public void onPause() { + prefs2Global(this); + super.onPause(); + } + public static void prefs2Global(Context context) { File previousCacheRoot = Global.thumbCacheRoot; File previousMapsForgeDir = Global.mapsForgeDir; @@ -241,8 +172,8 @@ public static void prefs2Global(Context context) { LibGlobal.preferLongXmpFormat = getPref(prefs, "xmp_file_schema_long", LibGlobal.preferLongXmpFormat); Global.mapsForgeEnabled = getPref(prefs, "mapsForgeEnabled", Global.mapsForgeEnabled); + AndroFotoFinderApp.setMediaImageDbReplacement(context, getPref(prefs, "useMediaImageDbReplacement", Global.useMediaImageDbReplacement)); - Global.imageDetailThumbnailIfBiggerThan = getPref(prefs, "imageDetailThumbnailIfBiggerThan" , Global.imageDetailThumbnailIfBiggerThan); Global.maxSelectionMarkersInMap = getPref(prefs, "maxSelectionMarkersInMap" , Global.maxSelectionMarkersInMap); @@ -285,6 +216,77 @@ public static void prefs2Global(Context context) { fixDefaults(context, previousCacheRoot, previousMapsForgeDir); } + @Override + protected void onCreate(final Bundle savedInstanceState) { + LocalizedActivity.fixLocale(this); // #21: Support to change locale at runtime + super.onCreate(savedInstanceState); + + if (Global.debugEnabled) { + // todo create junit integration tests with arabic locale from this. + StringFormatResourceTests.test(this); + } + + final Intent intent = getIntent(); + if (Global.debugEnabled && (intent != null)) { + Log.d(Global.LOG_CONTEXT, "SettingsActivity onCreate " + intent.toUri(Intent.URI_INTENT_SCHEME)); + } + + this.addPreferencesFromResource(R.xml.preferences); + this.addPreferencesFromResource(R.xml.preferences); + prefsInstance = PreferenceManager + .getDefaultSharedPreferences(this); + global2Prefs(this.getApplication()); + + // #21: Support to change locale at runtime + defaultLocalePreference = + (ListPreference) findPreference(Global.PREF_KEY_USER_LOCALE); + defaultLocalePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + setLanguage((String) newValue); + LocalizedActivity.recreate(SettingsActivity.this); + return true; // change is allowed + } + }); + + mediaUpdateStrategyPreference = + (ListPreference) findPreference("mediaUpdateStrategy"); + mediaUpdateStrategyPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + LibGlobal.mediaUpdateStrategy = (String) newValue; + setPref(LibGlobal.mediaUpdateStrategy, mediaUpdateStrategyPreference, R.array.pref_media_update_strategy_names); + return true; + } + }); + setPref(LibGlobal.mediaUpdateStrategy, mediaUpdateStrategyPreference, R.array.pref_media_update_strategy_names); + + findPreference("debugClearLog").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + onDebugClearLogCat(); + return false; // donot close + } + }); + findPreference("debugSaveLog").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + onDebugSaveLogCat(); + return false; // donot close + } + }); + findPreference("translate").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + onTranslate(); + return false; // donot close + } + }); + + // #21: Support to change locale at runtime + updateSummary(); + } + private static void fixDefaults(Context context, File previousCacheRoot, File previousMapsForgeDir) { boolean mustSave = false; diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/backup/BackupAsyncTask.java b/app/src/main/java/de/k3b/android/androFotoFinder/backup/BackupAsyncTask.java index b95c9dee..9f683891 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/backup/BackupAsyncTask.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/backup/BackupAsyncTask.java @@ -21,82 +21,39 @@ import android.app.Activity; import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; import android.util.Log; import android.widget.ProgressBar; import android.widget.TextView; import java.util.Date; -import java.util.concurrent.atomic.AtomicBoolean; -import de.k3b.android.androFotoFinder.Common; import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.R; +import de.k3b.android.widget.ProgressableAsyncTask; import de.k3b.io.IProgessListener; import de.k3b.zip.IZipConfig; import de.k3b.zip.LibZipGlobal; -import de.k3b.zip.ProgressFormatter; import de.k3b.zip.ZipConfigDto; import de.k3b.zip.ZipStorage; -/** - * ProgressData: Text that can be displayed as progress message - * in owning Activity. Translated from android independant {@link de.k3b.io.IProgessListener} - */ -class ProgressData { - final int itemcount; - final int size; - final String message; - - ProgressData(int itemcount, int size, String message) { - this.itemcount = itemcount; - this.size = size; - this.message = message; - } -} - /** * Async ancapsulation of * {@link de.k3b.android.androFotoFinder.backup.Backup2ZipService} */ -public class BackupAsyncTask extends AsyncTask implements IProgessListener { +public class BackupAsyncTask extends ProgressableAsyncTask implements IProgessListener { private final String mDebugPrefix = "BackupAsyncTask "; private final Backup2ZipService service; - private Activity activity; - private ProgressBar mProgressBar = null; - private TextView status; - private AtomicBoolean isActive = new AtomicBoolean(true); - - private final ProgressFormatter formatter; - // last known number of items to be processed - private int lastSize = 0; public BackupAsyncTask(Context context, ZipConfigDto mZipConfigData, ZipStorage zipStorage, Date backupDate) { this.service = new Backup2ZipService(context.getApplicationContext(), mZipConfigData, zipStorage, backupDate, BackupOptions.ALL); - - formatter = new ProgressFormatter(); } - /** - * (Re-)Attach owning Activity to BackupAsyncTask - * (i.e. after Device rotation - * - * @param why - * @param activity new owner - * @param progressBar To be updated while compression task is running - * @param status To be updated while compression task is running - */ + @Override public void setContext(String why, Activity activity, ProgressBar progressBar, TextView status) { - if (LibZipGlobal.debugEnabled) { - Log.d(LibZipGlobal.LOG_TAG, mDebugPrefix + why + " setContext " + activity); - } - this.activity = activity; - mProgressBar = progressBar; - this.status = status; + super.setContext(why, activity, progressBar, status); service.setProgessListener((progressBar != null) ? this : null); } @@ -136,71 +93,12 @@ protected void onPostExecute(IZipConfig zipConfig) { } } - private void finish(String why, int resultCode, CharSequence message) { - if (message != null) { - Intent intent = new Intent(); - intent.putExtra(Common.EXTRA_TITLE, message); - activity.setResult(resultCode, intent); - } else { - activity.setResult(resultCode); - } - activity.finish(); - - setContext(why + mDebugPrefix + " finish ", null, null, null); - - } - - /** called on error */ - @Override - protected void onCancelled() { - if (activity != null) { - if (LibZipGlobal.debugEnabled || Global.debugEnabled) { - Log.d(LibZipGlobal.LOG_TAG, activity.getClass().getSimpleName() + ": " + activity.getText(android.R.string.cancel)); - } - - finish(mDebugPrefix + " onCancelled ", Activity.RESULT_CANCELED, activity.getText(android.R.string.cancel)); - } - } - - public static boolean isActive(BackupAsyncTask backupAsyncTask) { - return (backupAsyncTask != null) && (backupAsyncTask.isActive.get()); - } - - /** - * de.k3b.io.IProgessListener: - * - * called every time when command makes some little progress in non gui thread. - * return true to continue - */ @Override public boolean onProgress(int itemcount, int size, String message) { - publishProgress(new ProgressData(itemcount, size, message)); - - final boolean cancelled = this.isCancelled(); - if (cancelled) { + boolean result = onProgress(itemcount, size, message); + if (isCancelled()) { if (this.service != null) this.service.cancel(); - if (LibZipGlobal.debugEnabled) { - Log.d(LibZipGlobal.LOG_TAG, mDebugPrefix + " cancel backup pressed "); - } - } - return !cancelled; - } - - /** called from {@link AsyncTask} in gui task */ - @Override - protected void onProgressUpdate(ProgressData... values) { - final ProgressData progressData = values[0]; - if (mProgressBar != null) { - int size = progressData.size; - if ((size != 0) && (size > lastSize)) { - mProgressBar.setMax(size); - lastSize = size; - } - mProgressBar.setProgress(progressData.itemcount); - } - if (this.status != null) { - this.status.setText(formatter.format(progressData.itemcount, progressData.size)); - // values[0].message); } + return result; } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/backup/BackupProgressActivity.java b/app/src/main/java/de/k3b/android/androFotoFinder/backup/BackupProgressActivity.java index 1c17f7b5..b21d7b73 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/backup/BackupProgressActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/backup/BackupProgressActivity.java @@ -29,9 +29,6 @@ import android.support.annotation.NonNull; import android.support.v4.provider.DocumentFile; import android.util.Log; -import android.view.View; -import android.widget.Button; -import android.widget.ProgressBar; import android.widget.TextView; import java.io.File; @@ -42,7 +39,8 @@ import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.R; import de.k3b.android.util.IntentUtil; -import de.k3b.android.widget.LocalizedActivity; +import de.k3b.android.widget.ProgressActivity; +import de.k3b.android.widget.ProgressableAsyncTask; import de.k3b.zip.IZipConfig; import de.k3b.zip.LibZipGlobal; import de.k3b.zip.ZipConfigDto; @@ -52,7 +50,7 @@ /** * #108: Showing progress while backup/compression-to-zip is executed */ -public class BackupProgressActivity extends LocalizedActivity { +public class BackupProgressActivity extends ProgressActivity { /** * document tree supported since andrid-5.0. For older devices use folder picker */ @@ -62,7 +60,7 @@ public class BackupProgressActivity extends LocalizedActivity { private static String mDebugPrefix = "BuProgressActivity: "; // != null while async backup is running - private static BackupAsyncTask backupAsyncTask = null; + private static ProgressableAsyncTask asyncTask = null; private static Date backupDate = null; private IZipConfig mZipConfigData = null; @@ -123,7 +121,17 @@ private static DocumentFile getDocFile(Context context, @NonNull String dir) { } -/* + @Override + protected ProgressableAsyncTask getAsyncTask() { + return asyncTask; + } + + @Override + protected void setAsyncTask(ProgressableAsyncTask asyncTask) { + BackupProgressActivity.asyncTask = asyncTask; + } + + /* @Override protected void onPause() { setBackupAsyncTaskProgessReceiver(null); @@ -141,16 +149,16 @@ protected void onCreate(Bundle savedInstanceState) { mZipConfigData = (IZipConfig) intent.getSerializableExtra(EXTRA_STATE_ZIP_CONFIG); - if (backupAsyncTask == null) { + if (getAsyncTask() == null) { backupDate = new Date(); final String zipDir = mZipConfigData.getZipDir(); final String zipName = ZipConfigDto.getZipFileName(mZipConfigData, backupDate); ZipStorage zipStorage = getCurrentStorage(this, zipDir, zipName); - backupAsyncTask = new BackupAsyncTask(this, new ZipConfigDto(mZipConfigData), zipStorage, - backupDate); - setBackupAsyncTaskProgessReceiver(mDebugPrefix + "onCreate create backupAsyncTask ", this); - backupAsyncTask.execute(); + setAsyncTask(new BackupAsyncTask(this, new ZipConfigDto(mZipConfigData), zipStorage, + backupDate)); + setAsyncTaskProgessReceiver(mDebugPrefix + "onCreate create asyncTask ", this); + getAsyncTask().execute(); } final TextView lblContext = (TextView) findViewById(R.id.lbl_context); @@ -165,60 +173,5 @@ protected void onCreate(Bundle savedInstanceState) { lblContext.setText(contextMessage); } - @Override - protected void onDestroy() { - setBackupAsyncTaskProgessReceiver(mDebugPrefix + "onDestroy ", null); - super.onDestroy(); - } - - @Override - protected void onResume() { - setBackupAsyncTaskProgessReceiver(mDebugPrefix + "onResume ", this); - Global.debugMemory(mDebugPrefix, "onResume"); - super.onResume(); - - } - - /** - * (Re-)Connects this activity back with static backupAsyncTask - */ - private void setBackupAsyncTaskProgessReceiver(String why, Activity progressReceiver) { - boolean isActive = BackupAsyncTask.isActive(backupAsyncTask); - boolean running = (progressReceiver != null) && isActive; - - String debugContext = why + mDebugPrefix + " setBackupAsyncTaskProgessReceiver isActive=" + isActive + - ", running=" + running + - " "; - - if (backupAsyncTask != null) { - final ProgressBar progressBar = (ProgressBar) this.findViewById(R.id.progressBar); - final TextView status = (TextView) this.findViewById(R.id.lbl_status); - final Button buttonCancel = (Button) this.findViewById(R.id.cmd_cancel); - - // setVisibility(running, progressBar, buttonCancel); - - if (running) { - backupAsyncTask.setContext(debugContext, this, progressBar, status); - final String _debugContext = debugContext; - buttonCancel.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (LibZipGlobal.debugEnabled) { - Log.d(LibZipGlobal.LOG_TAG, mDebugPrefix + " button Cancel backup pressed initialized by " + _debugContext); - } - backupAsyncTask.cancel(false); - buttonCancel.setVisibility(View.INVISIBLE); - } - }); - - } else { - backupAsyncTask.setContext(debugContext, null, null, null); - buttonCancel.setOnClickListener(null); - if (!isActive) { - backupAsyncTask = null; - } - } - } - } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/backup/package-info.java b/app/src/main/java/de/k3b/android/androFotoFinder/backup/package-info.java index 166a8b4f..c4cbd97c 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/backup/package-info.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/backup/package-info.java @@ -26,7 +26,7 @@ * * {@link de.k3b.android.androFotoFinder.backup.Backup2ZipService} : Collects Files to backed up from database via Filter * * {@link de.k3b.android.androFotoFinder.backup.ApmZipCompressJob} : Executes the compression (with android specific Filesystem) * * {@link de.k3b.zip.CompressJob} : Executes the compression (with android independant implementation) - * * {@link de.k3b.android.androFotoFinder.backup.ProgressData} : Data containing compression progress + * * {@link de.k3b.android.androFotoFinder.backup.BackupProgressActivity} : Data containing compression progress * * {@link de.k3b.io.IProgessListener} : android independant compression progress **/ package de.k3b.android.androFotoFinder.backup; 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 76150770..5a983f79 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 @@ -51,6 +51,12 @@ public static SQLiteDatabase getWritableDatabase(Context context) { return instance.getWritableDatabase(); } + public static void version2Upgrade_RecreateMediDbCopy(final SQLiteDatabase db) { + for (String sql : MediaImageDbReplacement.Impl.DDL) { + db.execSQL(sql); + } + } + /** * called if database doesn-t exist yet */ @@ -58,24 +64,18 @@ public static SQLiteDatabase getWritableDatabase(Context context) { public void onCreate(final SQLiteDatabase db) { db.execSQL(TransactionLogSql.CREATE_TABLE); - this.version2Upgrade_MediDbCopy(db); + this.version2Upgrade_RecreateMediDbCopy(db); } + private static DatabaseHelper instance = null; + @Override public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { 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_MediDbCopy(db); - } - } - - private static DatabaseHelper instance = null; - - private void version2Upgrade_MediDbCopy(final SQLiteDatabase db) { - for (String sql : MediaImageDbReplacement.Impl.DDL) { - db.execSQL(sql); + this.version2Upgrade_RecreateMediDbCopy(db); } } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBUpdater.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBUpdater.java new file mode 100644 index 00000000..146eded6 --- /dev/null +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBUpdater.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see + */ +package de.k3b.android.androFotoFinder.queries; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.widget.Toast; + +import java.util.Date; + +import de.k3b.io.IProgessListener; + +public class MediaDBUpdater { + private final Context context; + private final SQLiteDatabase writableDatabase; + + public MediaDBUpdater(Context context, SQLiteDatabase writableDatabase) { + this.context = context; + this.writableDatabase = writableDatabase; + } + + public void rebuild(Context context, IProgessListener progessListener) { + long start = new Date().getTime(); + clearMediaCopy(); + MediaImageDbReplacement.Impl.updateMedaiCopy(context, writableDatabase, 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 clearMediaCopy() { + DatabaseHelper.version2Upgrade_RecreateMediDbCopy(writableDatabase); + } +} 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 855f2029..92f546e9 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 @@ -23,6 +23,7 @@ import android.database.Cursor; import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteStatement; import android.net.Uri; import android.os.Build; import android.os.CancellationSignal; @@ -33,8 +34,10 @@ import de.k3b.LibGlobal; import de.k3b.android.androFotoFinder.Global; +import de.k3b.android.androFotoFinder.R; import de.k3b.database.QueryParameter; import de.k3b.io.AlbumFile; +import de.k3b.io.IProgessListener; import de.k3b.io.StringUtils; import de.k3b.io.VISIBILITY; @@ -117,11 +120,12 @@ private Cursor createCursorForQuery(StringBuilder out_debugMessage, String dbgCo excpetion = ex; } finally { if ((excpetion != null) || Global.debugEnabledSql || (out_debugMessage != null)) { + final int count = (query == null) ? 0 : query.getCount(); StringBuilder message = StringUtils.appendMessage(out_debugMessage, excpetion, dbgContext, MODUL_NAME + ".createCursorForQuery:\n", QueryParameter.toString(sqlSelectColums, null, Impl.table, sqlWhereStatement, - selectionArgs, sqlSortOrder, query.getCount())); + selectionArgs, sqlSortOrder, count)); if (out_debugMessage == null) { Log.i(Global.LOG_CONTEXT, message.toString(), excpetion); } // else logging is done by caller @@ -299,6 +303,7 @@ public static class Impl { * copied from android-4.4 android database. Removed columns not used */ public static final String[] DDL = new String[]{ + "DROP TABLE IF EXISTS \"files\"", "CREATE TABLE \"files\" (\n" + "\t_id INTEGER PRIMARY KEY AUTOINCREMENT,\n" + "\t_size INTEGER,\n" + @@ -392,6 +397,78 @@ private static boolean isDouble(int index) { return index >= dblMin && index <= dblMax; } + private static String getSqlInsertWithParams() { + StringBuilder sql = new StringBuilder(); + + sql.append("INSERT INTO ").append(table).append("(").append(USED_MEDIA_COLUMNS[0]); + for (int i = 1; i < USED_MEDIA_COLUMNS.length; i++) { + sql.append(", ").append(USED_MEDIA_COLUMNS[i]); + } + sql.append(") VALUES (?"); + for (int i = 1; i < USED_MEDIA_COLUMNS.length; i++) { + sql.append(", ?"); + } + sql.append(")"); + return sql.toString(); + } + + private static String getSqlUpdateWithParams() { + StringBuilder sql = new StringBuilder(); + + sql.append("UPDATE ").append(table).append(" SET "); + for (int i = 1; i < USED_MEDIA_COLUMNS.length; i++) { + if (i > 1) sql.append(", "); + sql.append(USED_MEDIA_COLUMNS[i]).append("=?"); + } + sql.append(" WHERE ").append(USED_MEDIA_COLUMNS[0]).append("=?"); + return sql.toString(); + } + + private static int bindAndExecUpdate(Cursor c, SQLiteStatement sql) { + sql.clearBindings(); + + // sql where + sql.bindLong(dblMax + 1, c.getLong(intMin)); + + for (int i = intMin + 1; i <= intMax; i++) { + if (!c.isNull(i)) { + sql.bindLong(i, c.getLong(i)); + } + } + for (int i = txtMin; i <= txtMax; i++) { + if (!c.isNull(i)) { + sql.bindString(i, c.getString(i)); + } + } + for (int i = dblMin; i <= dblMax; i++) { + if (!c.isNull(i)) { + sql.bindDouble(i, c.getDouble(i)); + } + } + return sql.executeUpdateDelete(); + } + + private static void bindAndExecInsert(Cursor c, SQLiteStatement sql) { + sql.clearBindings(); + + for (int i = intMin; i <= intMax; i++) { + if (!c.isNull(i)) { + sql.bindLong(i + 1, c.getLong(i)); + } + } + for (int i = txtMin; i <= txtMax; i++) { + if (!c.isNull(i)) { + sql.bindString(i + 1, c.getString(i)); + } + } + for (int i = dblMin; i <= dblMax; i++) { + if (!c.isNull(i)) { + sql.bindDouble(i + 1, c.getDouble(i)); + } + } + sql.executeInsert(); + } + private static ContentValues getContentValues(Cursor cursor, ContentValues destination) { destination.clear(); int colCount = cursor.getColumnCount(); @@ -411,8 +488,18 @@ private static ContentValues getContentValues(Cursor cursor, ContentValues desti return destination; } - public static int updateMedaiCopy(Context context, SQLiteDatabase db, Date lastUpdate) { - int changeCount = 0; + public static void clearMedaiCopy(SQLiteDatabase db) { + try { + db.execSQL("DROP TABLE " + table); + } catch (Exception ex) { + // Log.e(Global.LOG_CONTEXT, "FotoSql.execGetFotoPaths() Cannot get path from: " + FotoSql.SQL_COL_PATH + " like '" + pathFilter +"'", ex); + } finally { + } + } + + + public static int updateMedaiCopy(Context context, SQLiteDatabase db, Date lastUpdate, IProgessListener progessListener) { + int progress = 0; QueryParameter query = queryGetAllColumns; long _lastUpdate = (lastUpdate != null) ? (lastUpdate.getTime() / 1000L) : 0L; @@ -423,24 +510,63 @@ public static int updateMedaiCopy(Context context, SQLiteDatabase db, Date lastU // FotoSql.createCursorForQuery() } Cursor c = null; - ContentValues contentValues = new ContentValues(); + SQLiteStatement sqlInsert = null; + SQLiteStatement sqlUpdate = null; + SQLiteStatement lastSql = null; + boolean isUpdate = false; + // ContentValues contentValues = new ContentValues(); try { - c = ContentProviderMediaImpl.createCursorForQuery(null, "execGetFotoPaths(pathFilter)", context, + db.beginTransaction(); // Performance boost: all db-inserts/updates in one transaction + + if (progessListener != null) progessListener.onProgress(progress, 0, + context.getString(R.string.load_db_menu_title)); + + c = ContentProviderMediaImpl.createCursorForQuery(null, "updateMedaiCopy", context, query, null, null); + int itemCount = c.getCount(); + + sqlInsert = db.compileStatement(getSqlInsertWithParams()); + sqlUpdate = db.compileStatement(getSqlUpdateWithParams()); while (c.moveToNext()) { - getContentValues(c, contentValues); - save(db, c, contentValues, _lastUpdate); + // getContentValues(c, contentValues); + + isUpdate = (c.getLong(colDATE_ADDED) <= _lastUpdate); + + if (isUpdate) { + lastSql = sqlUpdate; + isUpdate = bindAndExecUpdate(c, sqlUpdate) > 0; + // 0 affected update rows: must insert + } + + if (!isUpdate) { + lastSql = sqlInsert; + bindAndExecInsert(c, sqlInsert); + } + + 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 + return -progress; + } + } + progress++; } + db.setTransactionSuccessful(); // This commits the transaction if there were no exceptions } 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, "MediaImageDbReplacement.updateMedaiCopy cannot insert/update: " + lastSql + " from " + c, ex); } finally { + sqlInsert.close(); + sqlUpdate.close(); + db.endTransaction(); if (c != null) c.close(); } if (Global.debugEnabled) { // Log.d(Global.LOG_CONTEXT, "FotoSql.execGetFotoPaths() result count=" + result.size()); } - return 0; + return progress; } private static void save(SQLiteDatabase db, Cursor c, ContentValues contentValues, long lastUpdate) { diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MergedMediaDB.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MergedMediaDB.java new file mode 100644 index 00000000..0939a6a5 --- /dev/null +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MergedMediaDB.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2019 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see + */ +package de.k3b.android.androFotoFinder.queries; + +import android.content.ContentValues; +import android.net.Uri; + +import de.k3b.io.VISIBILITY; + +/** + * #155: All reads are done through database while writes are + * applied to database and contentProvider. + *

+ * 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 and same pk. + */ +public class MergedMediaDB extends MediaDBApiWrapper { + private final IMediaDBApi database; + private final IMediaDBApi contentProvider; + + public MergedMediaDB(IMediaDBApi database, IMediaDBApi contentProvider) { + super(database, contentProvider); + this.database = database; + this.contentProvider = contentProvider; + } + + @Override + public int execUpdate(String dbgContext, long id, ContentValues values) { + int result = super.execUpdate(dbgContext, id, values); + database.execUpdate(dbgContext, id, values); + return result; + } + + @Override + public int execUpdate(String dbgContext, String path, ContentValues values, VISIBILITY visibility) { + int result = super.execUpdate(dbgContext, path, values, visibility); + database.execUpdate(dbgContext, path, values, visibility); + return result; + } + + @Override + public int exexUpdateImpl(String dbgContext, ContentValues values, String sqlWhere, String[] selectionArgs) { + int result = super.exexUpdateImpl(dbgContext, values, sqlWhere, selectionArgs); + database.exexUpdateImpl(dbgContext, values, sqlWhere, selectionArgs); + return result; + } + + /** + * 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 = contentProvider.execUpdate(dbgContext, dbUpdateFilterJpgFullPathName, + values, visibility); + + if (modifyCount == 0) { + // update failed (probably becauce oldFullPathName not found. try insert it. + FotoSql.addDateAdded(values); + + // insert into contentProvider and database + Uri uriWithId = execInsert(dbgContext, values); + result = FotoSql.getId(uriWithId); + } else { + // update into contentprovider successfull. also add to database + database.execUpdate(dbgContext, dbUpdateFilterJpgFullPathName, + values, visibility); + } + 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) { + Uri result = super.execInsert(dbgContext, values); + + // insert with same pk as contentprovider does + values.put(FotoSql.SQL_COL_PK, FotoSql.getId(result)); + database.execInsert(dbgContext, values); + values.remove(FotoSql.SQL_COL_PK); + return result; + } + + /** + * 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) { + int result = super.deleteMedia(dbgContext, where, selectionArgs, preventDeleteImageFile); + database.deleteMedia(dbgContext, where, selectionArgs, preventDeleteImageFile); + return result; + } +} diff --git a/app/src/main/java/de/k3b/android/widget/ProgressActivity.java b/app/src/main/java/de/k3b/android/widget/ProgressActivity.java new file mode 100644 index 00000000..1a1fa1c8 --- /dev/null +++ b/app/src/main/java/de/k3b/android/widget/ProgressActivity.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2019 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see + */ +package de.k3b.android.widget; + +import android.app.Activity; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.ProgressBar; +import android.widget.TextView; + +import de.k3b.android.androFotoFinder.Global; +import de.k3b.android.androFotoFinder.R; +import de.k3b.zip.LibZipGlobal; + +public abstract class ProgressActivity extends LocalizedActivity { + private static String mDebugPrefix = "ProgressActivity: "; + + abstract protected ProgressableAsyncTask getAsyncTask(); + + abstract protected void setAsyncTask(ProgressableAsyncTask asyncTask); + + @Override + protected void onDestroy() { + setAsyncTaskProgessReceiver(mDebugPrefix + "onDestroy ", null); + super.onDestroy(); + } + + @Override + protected void onResume() { + setAsyncTaskProgessReceiver(mDebugPrefix + "onResume ", this); + Global.debugMemory(mDebugPrefix, "onResume"); + super.onResume(); + + } + + /** + * (Re-)Connects this activity back with static asyncTask + */ + protected void setAsyncTaskProgessReceiver(String why, Activity progressReceiver) { + boolean isActive = ProgressableAsyncTask.isActive(getAsyncTask()); + boolean running = (progressReceiver != null) && isActive; + + String debugContext = why + mDebugPrefix + " setBackupAsyncTaskProgessReceiver isActive=" + isActive + + ", running=" + running + + " "; + + if (getAsyncTask() != null) { + final ProgressBar progressBar = (ProgressBar) this.findViewById(R.id.progressBar); + final TextView status = (TextView) this.findViewById(R.id.lbl_status); + final Button buttonCancel = (Button) this.findViewById(R.id.cmd_cancel); + + // setVisibility(running, progressBar, buttonCancel); + + if (running) { + getAsyncTask().setContext(debugContext, this, progressBar, status); + final String _debugContext = debugContext; + buttonCancel.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (LibZipGlobal.debugEnabled) { + Log.d(LibZipGlobal.LOG_TAG, mDebugPrefix + " button Cancel backup pressed initialized by " + _debugContext); + } + getAsyncTask().cancel(false); + buttonCancel.setVisibility(View.INVISIBLE); + } + }); + + } else { + getAsyncTask().setContext(debugContext, null, null, null); + buttonCancel.setOnClickListener(null); + if (!isActive) { + setAsyncTask(null); + } + } + } + } +} diff --git a/app/src/main/java/de/k3b/android/widget/ProgressableAsyncTask.java b/app/src/main/java/de/k3b/android/widget/ProgressableAsyncTask.java new file mode 100644 index 00000000..ca74abd7 --- /dev/null +++ b/app/src/main/java/de/k3b/android/widget/ProgressableAsyncTask.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2019 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see + */ +package de.k3b.android.widget; + +import android.app.Activity; +import android.content.Intent; +import android.os.AsyncTask; +import android.util.Log; +import android.widget.ProgressBar; +import android.widget.TextView; + +import java.util.concurrent.atomic.AtomicBoolean; + +import de.k3b.android.androFotoFinder.Common; +import de.k3b.android.androFotoFinder.Global; +import de.k3b.io.IProgessListener; +import de.k3b.zip.LibZipGlobal; +import de.k3b.zip.ProgressFormatter; + +public abstract class ProgressableAsyncTask extends AsyncTask implements IProgessListener { + protected final ProgressFormatter formatter = new ProgressFormatter(); + private final String mDebugPrefix = "ProgressableAsyncTask "; + protected Activity activity; + protected AtomicBoolean isActive = new AtomicBoolean(true); + // last known number of items to be processed + protected int lastSize = 0; + private ProgressBar mProgressBar = null; + private TextView status; + + public static boolean isActive(ProgressableAsyncTask backupAsyncTask) { + return (backupAsyncTask != null) && (backupAsyncTask.isActive.get()); + } + + /** + * (Re-)Attach owning Activity to BackupAsyncTask + * (i.e. after Device rotation + * + * @param why + * @param activity new owner + * @param progressBar To be updated while compression task is running + * @param status To be updated while compression task is running + */ + public void setContext(String why, Activity activity, ProgressBar progressBar, TextView status) { + if (LibZipGlobal.debugEnabled) { + Log.d(LibZipGlobal.LOG_TAG, mDebugPrefix + why + " setContext " + activity); + } + this.activity = activity; + mProgressBar = progressBar; + this.status = status; + } + + /** + * called on error + */ + @Override + protected void onCancelled() { + if (activity != null) { + if (LibZipGlobal.debugEnabled || Global.debugEnabled) { + Log.d(LibZipGlobal.LOG_TAG, activity.getClass().getSimpleName() + ": " + activity.getText(android.R.string.cancel)); + } + + finish(mDebugPrefix + " onCancelled ", Activity.RESULT_CANCELED, activity.getText(android.R.string.cancel)); + } + } + + protected void finish(String why, int resultCode, CharSequence message) { + if (message != null) { + Intent intent = new Intent(); + intent.putExtra(Common.EXTRA_TITLE, message); + activity.setResult(resultCode, intent); + } else { + activity.setResult(resultCode); + } + activity.finish(); + + setContext(why + mDebugPrefix + " finish ", null, null, null); + + } + + /** + * de.k3b.io.IProgessListener: + *

+ * called every time when command makes some little progress in non gui thread. + * return true to continue + */ + @Override + public boolean onProgress(int itemcount, int size, String message) { + publishProgress(new ProgressData(itemcount, size, message)); + + final boolean cancelled = this.isCancelled(); + if (cancelled) { + if (LibZipGlobal.debugEnabled) { + Log.d(LibZipGlobal.LOG_TAG, mDebugPrefix + " cancel backup pressed "); + } + } + return !cancelled; + } + + + /** + * called from {@link AsyncTask} in gui task + */ + @Override + protected void onProgressUpdate(ProgressData... values) { + final ProgressData progressData = values[0]; + if (mProgressBar != null) { + int size = progressData.size; + if ((size != 0) && (size > lastSize)) { + mProgressBar.setMax(size); + lastSize = size; + } + mProgressBar.setProgress(progressData.itemcount); + } + if (this.status != null) { + this.status.setText(formatter.format(progressData.itemcount, progressData.size)); + // values[0].message); + } + } +} + +/** + * ProgressData: Text that can be displayed as progress message + * in owning Activity. Translated from android independant {@link de.k3b.io.IProgessListener} + */ +class ProgressData { + final int itemcount; + final int size; + final String message; + + ProgressData(int itemcount, int size, String message) { + this.itemcount = itemcount; + this.size = size; + this.message = message; + } +} + diff --git a/app/src/main/res/menu/menu_ao10.xml b/app/src/main/res/menu/menu_ao10.xml new file mode 100644 index 00000000..f2749423 --- /dev/null +++ b/app/src/main/res/menu/menu_ao10.xml @@ -0,0 +1,10 @@ + +

+ + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 38ec4e98..8030dfd9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -279,5 +279,8 @@ You can undo hiding by calling the mediascanner from gallery-menu."
< ! - - what type of data should be saven ? - - > What: --> + + + (Re)Load Media Database diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 671db294..343f0a22 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -126,6 +126,12 @@ this program. If not, see + + ... lists) { return result; } - private static boolean append(StringBuilder result, String blockPrefix, List list, String delimiter, String before, String after) { + private static boolean append(StringBuilder result, String blockPrefix, List list, String seperator, String before, String after) { if (isNotEmpty(list)) { if (blockPrefix != null) { result.append(blockPrefix); @@ -519,8 +519,8 @@ private static boolean append(StringBuilder result, String blockPrefix, List list) { + return toSeperatedFieldListOrNull(list, ", "); + } + + private static String toSeperatedFieldListOrNull(List list, String seperator) { if (isNotEmpty(list)) { StringBuilder result = new StringBuilder(); - append(result, null, list, ", ", null, null); + append(result, null, list, seperator, null, null); return result.toString(); } return null; From fb9594aa62b0ae65c3a14e024d4c4df73880a80a Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Thu, 19 Dec 2019 01:12:22 +0100 Subject: [PATCH 30/37] #155: Made seperate logging for MediaImageDbReplacement / FotoSql / Contentprovider --- .../androFotoFinder/AndroFotoFinderApp.java | 16 ++++-- .../androFotoFinder/SettingsActivity.java | 2 +- .../queries/ContentProviderMediaImpl.java | 19 +++---- .../androFotoFinder/queries/FotoSql.java | 35 ++++++------- .../queries/MediaImageDbReplacement.java | 49 ++++++++++++++----- .../main/java/de/k3b/android/util/LogCat.java | 5 +- 6 files changed, 80 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java index 8052fe7b..28dcff8f 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java @@ -19,6 +19,7 @@ package de.k3b.android.androFotoFinder; +import android.app.Activity; import android.app.Application; import android.content.Context; import android.database.sqlite.SQLiteDatabase; @@ -37,6 +38,7 @@ import de.k3b.LibGlobal; import de.k3b.android.GuiUtil; import de.k3b.android.androFotoFinder.imagedetail.HugeImageLoader; +import de.k3b.android.androFotoFinder.queries.ContentProviderMediaImpl; import de.k3b.android.androFotoFinder.queries.DatabaseHelper; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.androFotoFinder.queries.FotoSqlBase; @@ -155,7 +157,10 @@ public static RefWatcher getRefWatcher(Context context) { mCrashSaveToFile = new LogCat(Global.LOG_CONTEXT, HugeImageLoader.LOG_TAG, PhotoViewAttacher.LOG_TAG, CupcakeGestureDetector.LOG_TAG, LibGlobal.LOG_TAG, ThumbNailUtils.LOG_TAG, IMapView.LOGTAG, - ExifInterface.LOG_TAG, PhotoPropertiesImageReader.LOG_TAG) { + ExifInterface.LOG_TAG, PhotoPropertiesImageReader.LOG_TAG, + FotoSql.LOG_TAG, + MediaImageDbReplacement.LOG_TAG, + ContentProviderMediaImpl.LOG_TAG) { @Override public void uncaughtException(Thread thread, Throwable ex) { @@ -165,13 +170,14 @@ public void uncaughtException(Thread thread, Throwable ex) { super.uncaughtException(thread, ex); } - public void saveToFile() { + public void saveToFile(Activity activity) { final File logFile = getOutpuFile(); String message = (logFile != null) ? "saving errorlog ('LocCat') to " + logFile.getAbsolutePath() : "Saving errorlog ('LocCat') is disabled. See Settings 'Diagnostics' for details"; Log.e(Global.LOG_CONTEXT, message); - Toast.makeText(AndroFotoFinderApp.this , message, Toast.LENGTH_LONG).show(); + final Context context = (activity != null) ? activity : AndroFotoFinderApp.this; + Toast.makeText(context, message, Toast.LENGTH_LONG).show(); saveLogCat(logFile, null, mTags); } @@ -226,9 +232,9 @@ public void onTerminate() { super.onTerminate(); } - public void saveToFile() { + public void saveToFile(Activity activity) { if (mCrashSaveToFile != null) { - mCrashSaveToFile.saveToFile(); + mCrashSaveToFile.saveToFile(activity); } } public void clear() { diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java b/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java index bdd79fa9..3ead356f 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java @@ -427,7 +427,7 @@ private void onDebugClearLogCat() { private void onDebugSaveLogCat() { Log.e(Global.LOG_CONTEXT, "SettingsActivity-SaveLogCat(): " + ActivityWithCallContext.readCallContext(getIntent())); - ((AndroFotoFinderApp) getApplication()).saveToFile(); + ((AndroFotoFinderApp) getApplication()).saveToFile(this); } private void onTranslate() { 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 8e8038b8..a9cd82b2 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 @@ -41,6 +41,7 @@ * Static Implementation of Context.getContentResolver()-ContentProvider based media api */ public class ContentProviderMediaImpl { + public static final String LOG_TAG = FotoSql.LOG_TAG + "Content"; private static final String MODUL_NAME = ContentProviderMediaImpl.class.getName(); public static Cursor createCursorForQuery( @@ -82,7 +83,7 @@ static Cursor createCursorForQuery( QueryParameter.toString(sqlSelectColums, null, from, sqlWhereStatement, sqlWhereParameters, sqlSortOrder, query.getCount())); if (out_debugMessage == null) { - Log.i(Global.LOG_CONTEXT, message.toString(), excpetion); + Log.i(LOG_TAG, message.toString(), excpetion); } // else logging is done by caller } } @@ -108,7 +109,7 @@ 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 + ":" + + Log.i(LOG_TAG, dbgContext + ":" + MODUL_NAME + ".exexUpdate " + excpetion + "\n" + QueryParameter.toString(null, values.toString(), FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, @@ -155,7 +156,7 @@ 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 + ":" + + Log.i(LOG_TAG, dbgContext + ":" + MODUL_NAME + ".execInsert " + excpetion + " " + values.toString() + " => " + result + " " + excpetion, excpetion); @@ -187,7 +188,7 @@ 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: " + + Log.i(LOG_TAG, dbgContext + "-b: " + MODUL_NAME + ".deleteMedia delete\n" + QueryParameter.toString(null, null, FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, @@ -197,7 +198,7 @@ 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 + ": " + + Log.i(LOG_TAG, dbgContext + ": " + MODUL_NAME + ".deleteMedia\ndelete " + QueryParameter.toString(null, null, @@ -213,7 +214,7 @@ public static int deleteMedia(String dbgContext, Context context, String where, 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); + Log.e(LOG_TAG, msg, ex); } return delCount; @@ -262,7 +263,7 @@ private static int _del_execRenameFolder_batch_not_working(Context context, Stri .build()); } } catch (Exception ex) { - Log.e(Global.LOG_CONTEXT, dbgContext + "-getAffected error :", ex); + Log.e(LOG_TAG, dbgContext + "-getAffected error :", ex); return -1; } finally { if (c != null) c.close(); @@ -273,7 +274,7 @@ private static int _del_execRenameFolder_batch_not_working(Context context, Stri } catch (Exception ex) { // java.lang.IllegalArgumentException: Unknown authority content://media/external/file // i assume not batch support for file - Log.e(Global.LOG_CONTEXT, dbgContext + "-updateAffected error :", ex); + Log.e(LOG_TAG, dbgContext + "-updateAffected error :", ex); return -1; } return ops.size(); @@ -291,7 +292,7 @@ public static ContentValues getDbContent(Context context, final long id) { return values; } } catch (Exception ex) { - Log.e(Global.LOG_CONTEXT, MODUL_NAME + + Log.e(LOG_TAG, 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/FotoSql.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/FotoSql.java index f90cc2e2..dee35020 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 @@ -62,6 +62,7 @@ * Created by k3b on 04.06.2015. */ public class FotoSql extends FotoSqlBase { + public static final String LOG_TAG = Global.LOG_CONTEXT + "-sql"; public static final int SORT_BY_DATE_OLD = 1; public static final int SORT_BY_NAME_OLD = 2; @@ -298,7 +299,7 @@ public static final double getGroupFactor(final double _zoomLevel) { } if (Global.debugEnabled) { - Log.e(Global.LOG_CONTEXT, "FotoSql.getGroupFactor(" + _zoomLevel + ") => " + result); + Log.e(FotoSql.LOG_TAG, "FotoSql.getGroupFactor(" + _zoomLevel + ") => " + result); } return result; @@ -632,7 +633,7 @@ public static QueryParameter getQuery(int queryID) { case QUERY_TYPE_GROUP_MOVE: return null; default: - Log.e(Global.LOG_CONTEXT, "FotoSql.getQuery(" + queryID + "): unknown ID"); + Log.e(FotoSql.LOG_TAG, "FotoSql.getQuery(" + queryID + "): unknown ID"); return null; } } @@ -757,7 +758,7 @@ public static boolean set(GalleryFilterParameter dest, String selectedAbsolutePa return true; default:break; } - Log.e(Global.LOG_CONTEXT, "FotoSql.setFilter(queryTypeId = " + queryTypeId + ") : unknown type"); + Log.e(FotoSql.LOG_TAG, "FotoSql.setFilter(queryTypeId = " + queryTypeId + ") : unknown type"); return false; } @@ -777,7 +778,7 @@ public static String execGetFotoPath(Uri uriWithID) { return DBUtils.getString(c,FotoSql.SQL_COL_PATH, null); } } catch (Exception ex) { - Log.e(Global.LOG_CONTEXT, "FotoSql.execGetFotoPath() Cannot get path from " + uriWithID, ex); + Log.e(FotoSql.LOG_TAG, "FotoSql.execGetFotoPath() Cannot get path from " + uriWithID, ex); } finally { if (c != null) c.close(); } @@ -803,13 +804,13 @@ public static List execGetFotoPaths(String pathFilter) { result.add(c.getString(0)); } } catch (Exception ex) { - Log.e(Global.LOG_CONTEXT, "FotoSql.execGetFotoPaths() Cannot get path from: " + FotoSql.SQL_COL_PATH + " like '" + pathFilter +"'", ex); + Log.e(FotoSql.LOG_TAG, "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()); + Log.d(FotoSql.LOG_TAG, "FotoSql.execGetFotoPaths() result count=" + result.size()); } return result; } @@ -852,13 +853,13 @@ public static IGeoRectangle execGetGeoRectangle(StringBuilder out_debugMessage, return result; } } catch (Exception ex) { - Log.e(Global.LOG_CONTEXT, "FotoSql.execGetGeoRectangle(): error executing " + query, ex); + Log.e(FotoSql.LOG_TAG, "FotoSql.execGetGeoRectangle(): error executing " + query, ex); } finally { if (c != null) c.close(); if (debugMessage != null) { StringUtils.appendMessage(debugMessage, "result", result); if (out_debugMessage == null) { - Log.i(Global.LOG_CONTEXT, debugMessage.toString()); + Log.i(FotoSql.LOG_TAG, debugMessage.toString()); } } } @@ -893,13 +894,13 @@ public static IGeoPoint execGetPosition(StringBuilder out_debugMessage, return result; } } catch (Exception ex) { - Log.e(Global.LOG_CONTEXT, "FotoSql.execGetPosition: error executing " + query, ex); + Log.e(FotoSql.LOG_TAG, "FotoSql.execGetPosition: error executing " + query, ex); } finally { if (c != null) c.close(); if (debugMessage != null) { StringUtils.appendMessage(debugMessage, "result", result); if (out_debugMessage == null) { - Log.i(Global.LOG_CONTEXT, debugMessage.toString()); + Log.i(FotoSql.LOG_TAG, debugMessage.toString()); } // else logging by caller } } @@ -927,7 +928,7 @@ public static Map execGetPathIdMap(String... fileNames) { result.put(c.getString(1),c.getLong(0)); } } catch (Exception ex) { - Log.e(Global.LOG_CONTEXT, "FotoSql.execGetPathIdMap: error executing " + query, ex); + Log.e(FotoSql.LOG_TAG, "FotoSql.execGetPathIdMap: error executing " + query, ex); } finally { if (c != null) c.close(); } @@ -1062,7 +1063,7 @@ public static Long getId(Uri uriWithId) { try { imageID = (idString == null) ? null : Long.valueOf(idString); } catch (NumberFormatException e) { - Log.e(Global.LOG_CONTEXT, "FotoSql.getId(" + uriWithId + ") => " + e.getMessage()); + Log.e(FotoSql.LOG_TAG, "FotoSql.getId(" + uriWithId + ") => " + e.getMessage()); } } return imageID; @@ -1106,7 +1107,7 @@ public static String getMinFolder(QueryParameter query, return c.getString(0); } } catch (Exception ex) { - Log.e(Global.LOG_CONTEXT, "FotoSql.getMinFolder() error :", ex); + Log.e(FotoSql.LOG_TAG, "FotoSql.getMinFolder() error :", ex); } finally { if (c != null) c.close(); } @@ -1125,7 +1126,7 @@ public static long getCount(QueryParameter query) { return c.getLong(0); } } catch (Exception ex) { - Log.e(Global.LOG_CONTEXT, "FotoSql.getCount() error :", ex); + Log.e(FotoSql.LOG_TAG, "FotoSql.getCount() error :", ex); } finally { if (c != null) c.close(); } @@ -1172,7 +1173,7 @@ public static CharSequence getStatisticsMessage(Context context, int prefixStrin ); } } catch (Exception ex) { - Log.e(Global.LOG_CONTEXT, "FotoSql.getStatisticsMessage() error :", ex); + Log.e(FotoSql.LOG_TAG, "FotoSql.getStatisticsMessage() error :", ex); } finally { if (c != null) c.close(); } @@ -1200,13 +1201,13 @@ private static SelectedFiles getSelectedfiles(QueryParameter query, String colna result = new SelectedFiles(paths, ids, null); } catch (Exception ex) { - Log.e(Global.LOG_CONTEXT, "FotoSql.getSelectedfiles() error :", ex); + Log.e(FotoSql.LOG_TAG, "FotoSql.getSelectedfiles() error :", ex); } finally { if (c != null) c.close(); } if (Global.debugEnabled) { - Log.d(Global.LOG_CONTEXT, "FotoSql.getSelectedfiles result count=" + ((result != null) ? result.size():0)); + Log.d(FotoSql.LOG_TAG, "FotoSql.getSelectedfiles result count=" + ((result != null) ? result.size() : 0)); } return result; 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 92f546e9..82f2a260 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 @@ -65,6 +65,8 @@ * Therefore apm uses a copy of contentprovider MediaStore.Images with same column names. */ public class MediaImageDbReplacement implements IMediaDBApi { + public static final String LOG_TAG = FotoSql.LOG_TAG + "DB"; + private static final String MODUL_NAME = ContentProviderMediaImpl.class.getName(); private final SQLiteDatabase db; @@ -127,7 +129,7 @@ private Cursor createCursorForQuery(StringBuilder out_debugMessage, String dbgCo QueryParameter.toString(sqlSelectColums, null, Impl.table, sqlWhereStatement, selectionArgs, sqlSortOrder, count)); if (out_debugMessage == null) { - Log.i(Global.LOG_CONTEXT, message.toString(), excpetion); + Log.i(LOG_TAG, message.toString(), excpetion); } // else logging is done by caller } } @@ -155,7 +157,7 @@ public int exexUpdateImpl(String dbgContext, ContentValues values, String sqlWhe excpetion = ex; } finally { if ((excpetion != null) || ((dbgContext != null) && (Global.debugEnabledSql || LibGlobal.debugEnabledJpg))) { - Log.i(Global.LOG_CONTEXT, dbgContext + ":" + + Log.i(LOG_TAG, dbgContext + ":" + MODUL_NAME + ".exexUpdate " + excpetion + "\n" + QueryParameter.toString(null, values.toString(), FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, @@ -209,7 +211,7 @@ public Uri execInsert(String dbgContext, ContentValues values) { excpetion = ex; } finally { if ((excpetion != null) || Global.debugEnabledSql || LibGlobal.debugEnabledJpg) { - Log.i(Global.LOG_CONTEXT, dbgContext + ":" + + Log.i(LOG_TAG, dbgContext + ":" + MODUL_NAME + ".execInsert " + excpetion + " " + values.toString() + " => " + result + " " + excpetion, excpetion); @@ -246,7 +248,7 @@ public int deleteMedia(String dbgContext, String where, String[] selectionArgs, lastSelectionArgs = null; delCount = db.delete(Impl.table, lastUsedWhereClause, lastSelectionArgs); if (Global.debugEnabledSql || LibGlobal.debugEnabledJpg) { - Log.i(Global.LOG_CONTEXT, dbgContext + "-b: " + + Log.i(LOG_TAG, dbgContext + "-b: " + MODUL_NAME + ".deleteMedia delete\n" + QueryParameter.toString(null, null, FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, @@ -255,7 +257,7 @@ public int deleteMedia(String dbgContext, String where, String[] selectionArgs, } else { delCount = db.delete(Impl.table, lastUsedWhereClause, lastSelectionArgs); if (Global.debugEnabledSql || LibGlobal.debugEnabledJpg) { - Log.i(Global.LOG_CONTEXT, dbgContext + ": " + + Log.i(LOG_TAG, dbgContext + ": " + MODUL_NAME + ".deleteMedia\ndelete " + QueryParameter.toString(null, null, @@ -271,7 +273,7 @@ public int deleteMedia(String dbgContext, String where, String[] selectionArgs, 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); + Log.e(LOG_TAG, msg, ex); } return delCount; @@ -289,7 +291,7 @@ public ContentValues getDbContent(long id) { return values; } } catch (Exception ex) { - Log.e(Global.LOG_CONTEXT, MODUL_NAME + + Log.e(LOG_TAG, MODUL_NAME + ".getDbContent(id=" + id + ") failed", ex); } finally { if (c != null) c.close(); @@ -492,7 +494,7 @@ public static void clearMedaiCopy(SQLiteDatabase db) { try { db.execSQL("DROP TABLE " + table); } catch (Exception ex) { - // Log.e(Global.LOG_CONTEXT, "FotoSql.execGetFotoPaths() Cannot get path from: " + FotoSql.SQL_COL_PATH + " like '" + pathFilter +"'", ex); + // Log.e(LOG_TAG, "FotoSql.execGetFotoPaths() Cannot get path from: " + FotoSql.SQL_COL_PATH + " like '" + pathFilter +"'", ex); } finally { } } @@ -500,6 +502,7 @@ public static void clearMedaiCopy(SQLiteDatabase db) { public static int updateMedaiCopy(Context context, SQLiteDatabase db, Date lastUpdate, IProgessListener progessListener) { int progress = 0; + java.util.Date startTime = new java.util.Date(); QueryParameter query = queryGetAllColumns; long _lastUpdate = (lastUpdate != null) ? (lastUpdate.getTime() / 1000L) : 0L; @@ -514,6 +517,9 @@ public static int updateMedaiCopy(Context context, SQLiteDatabase db, Date lastU SQLiteStatement sqlUpdate = null; SQLiteStatement lastSql = null; boolean isUpdate = false; + 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 @@ -521,9 +527,9 @@ public static int updateMedaiCopy(Context context, SQLiteDatabase db, Date lastU if (progessListener != null) progessListener.onProgress(progress, 0, context.getString(R.string.load_db_menu_title)); - c = ContentProviderMediaImpl.createCursorForQuery(null, "updateMedaiCopy", context, + c = ContentProviderMediaImpl.createCursorForQuery(null, "updateMedaiCopy-source", context, query, null, null); - int itemCount = c.getCount(); + itemCount = c.getCount(); sqlInsert = db.compileStatement(getSqlInsertWithParams()); sqlUpdate = db.compileStatement(getSqlUpdateWithParams()); @@ -533,12 +539,14 @@ public static int updateMedaiCopy(Context context, SQLiteDatabase db, Date lastU isUpdate = (c.getLong(colDATE_ADDED) <= _lastUpdate); if (isUpdate) { + updateCount++; lastSql = sqlUpdate; isUpdate = bindAndExecUpdate(c, sqlUpdate) > 0; // 0 affected update rows: must insert } if (!isUpdate) { + insertCout++; lastSql = sqlInsert; bindAndExecInsert(c, sqlInsert); } @@ -554,8 +562,25 @@ public static int updateMedaiCopy(Context context, SQLiteDatabase db, Date lastU progress++; } db.setTransactionSuccessful(); // This commits the transaction if there were no exceptions + if (Global.debugEnabledSql) { + java.util.Date endTime = new java.util.Date(); + final String message = "MediaImageDbReplacement.updateMedaiCopy(inserted:" + insertCout + + ", updated:" + updateCount + + ", toal:" + progress + + " / " + itemCount + + ") in " + ((endTime.getTime() - startTime.getTime()) / 1000) + + " Secs"; + Log.i(LOG_TAG, message); + } } catch (Exception ex) { - Log.e(Global.LOG_CONTEXT, "MediaImageDbReplacement.updateMedaiCopy cannot insert/update: " + lastSql + " from " + c, ex); + java.util.Date endTime = new java.util.Date(); + final String message = "MediaImageDbReplacement.updateMedaiCopy(inserted:" + insertCout + + ", updated:" + updateCount + + ", toal:" + progress + + " / " + itemCount + + ") in " + ((endTime.getTime() - startTime.getTime()) / 1000) + + " Secs"; + Log.e(LOG_TAG, "Cannot insert/update: " + lastSql + " from " + c + " in " + message, ex); } finally { sqlInsert.close(); sqlUpdate.close(); @@ -564,7 +589,7 @@ public static int updateMedaiCopy(Context context, SQLiteDatabase db, Date lastU } if (Global.debugEnabled) { - // Log.d(Global.LOG_CONTEXT, "FotoSql.execGetFotoPaths() result count=" + result.size()); + // Log.d(LOG_TAG, "FotoSql.execGetFotoPaths() result count=" + result.size()); } return progress; } diff --git a/app/src/main/java/de/k3b/android/util/LogCat.java b/app/src/main/java/de/k3b/android/util/LogCat.java index ba82e7af..81eaee9a 100644 --- a/app/src/main/java/de/k3b/android/util/LogCat.java +++ b/app/src/main/java/de/k3b/android/util/LogCat.java @@ -19,6 +19,7 @@ package de.k3b.android.util; +import android.app.Activity; import android.util.Log; import java.io.File; @@ -57,7 +58,7 @@ public LogCat(String... tags) { Thread.setDefaultUncaughtExceptionHandler(this); } - public abstract void saveToFile(); + public abstract void saveToFile(Activity activity); protected void saveLogCat(File logFile, OutputStream outputStream, String[] tags) { StringBuilder cmdline = new StringBuilder(); @@ -122,7 +123,7 @@ public void uncaughtException(Thread thread, Throwable ex) { try { // Do your stuff with the exception Log.e(mTags[0],"LogCat.uncaughtException " + ex, ex); - saveToFile(); + saveToFile(null); } catch (Exception e) { /* Ignore */ } finally { From 731b0ecc7c66b16228e8911929cec486ecdd49b2 Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Wed, 25 Dec 2019 23:28:37 +0100 Subject: [PATCH 31/37] #155: ImageDetailActivityViewPager refresh query after change --- .../androFotoFinder/AndroFotoFinderApp.java | 5 + .../android/androFotoFinder/LockScreen.java | 3 + .../ImageDetailActivityViewPager.java | 745 +++++++++--------- .../androFotoFinder/queries/IMediaDBApi.java | 10 + .../queries/MediaDBApiWrapper.java | 31 +- .../queries/MediaDBContentprovider.java | 26 + .../queries/MediaImageDbReplacement.java | 119 ++- .../queries/MergedMediaDB.java | 2 +- .../androFotoFinder/tagDB/TagWorflow.java | 36 +- .../k3b/android/util/AndroidFileCommands.java | 17 + .../k3b/android/widget/LocalizedActivity.java | 46 +- 11 files changed, 616 insertions(+), 424 deletions(-) diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java index 28dcff8f..cd652893 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java @@ -50,6 +50,7 @@ import de.k3b.android.osmdroid.forge.MapsForgeSupport; import de.k3b.android.util.LogCat; import de.k3b.android.widget.ActivityWithCallContext; +import de.k3b.android.widget.LocalizedActivity; import de.k3b.database.QueryParameter; import de.k3b.io.PhotoAutoprocessingDto; import de.k3b.media.ExifInterface; @@ -93,6 +94,10 @@ public static String getGetTeaserText(Context context, String linkUrlForDetails) public static void setMediaImageDbReplacement(Context context, boolean useMediaImageDbReplacement) { final IMediaDBApi oldMediaDBApi = FotoSql.getMediaDBApi(); if ((oldMediaDBApi == null) || (Global.useMediaImageDbReplacement != useMediaImageDbReplacement)) { + + // menu must be recreated + LocalizedActivity.setMustRecreate(); + Global.useMediaImageDbReplacement = useMediaImageDbReplacement; final MediaDBContentprovider mediaDBContentprovider = new MediaDBContentprovider(context); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/LockScreen.java b/app/src/main/java/de/k3b/android/androFotoFinder/LockScreen.java index cfffb82c..f9e2ce64 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/LockScreen.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/LockScreen.java @@ -28,6 +28,7 @@ import android.view.MenuItem; import de.k3b.android.util.MenuUtils; +import de.k3b.android.widget.LocalizedActivity; /** * #105: Management of app locking (aka Android "Screen"-pinning, "Kiosk Mode", "LockTask") @@ -52,6 +53,7 @@ public static boolean onOptionsItemSelected(Activity parent, MenuItem item) { Global.locked = true; SettingsActivity.global2Prefs(parent.getApplication()); } + LocalizedActivity.setMustRecreate(); } return true; case R.id.cmd_app_unpin2: @@ -59,6 +61,7 @@ public static boolean onOptionsItemSelected(Activity parent, MenuItem item) { Global.locked = false; SettingsActivity.global2Prefs(parent.getApplication()); parent.invalidateOptionsMenu(); + LocalizedActivity.setMustRecreate(); return true; } return false; diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java index a98bbc58..4a8b5f2d 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java @@ -165,82 +165,7 @@ public class ImageDetailActivityViewPager extends ActivityWithAutoCloseDialogs i // if not null this one image that cannot be translated to a file uri will be shown private Uri imageUri = null; - /** executes sql to load image detail data in a background task that may survive - * conriguration change (i.e. device rotation) */ - class LocalCursorLoader implements LoaderManager.LoaderCallbacks { - /** incremented every time a new curster/query is generated */ - private int mRequeryInstanceCount = 0; - - /** called by LoaderManager.getLoader(ACTIVITY_ID) to (re)create loader - * that attaches to last query/cursor if it still exist i.e. after rotation */ - @Override - public Loader onCreateLoader(int loaderID, Bundle bundle) { - switch (loaderID) { - case ACTIVITY_ID: - mRequeryInstanceCount++; - mWaitingForMediaScannerResult = false; - if (Global.debugEnabledSql) { - Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onCreateLoader" + - getDebugContext() + - " : query = " + mGalleryContentQuery); - } - return FotoSql.createCursorLoader(getApplicationContext(), mGalleryContentQuery); - default: - // An invalid id was passed in - return null; - } - } - - /** called after media db content has changed */ - @Override - public void onLoadFinished(Loader loader, Cursor data) { - // to be restored after refreshLocal if there is no mInitialFilePath - if ((mInitialScrollPosition == NO_INITIAL_SCROLL_POSITION) && (mViewPager != null)) { - mInitialScrollPosition = mViewPager.getCurrentItem(); - } - // do change the data - mAdapter.swapCursor(data); - - // restore position is invalid - final int newItemCount = mAdapter.getCount(); - - if (((newItemCount == 0)) || (mInitialScrollPosition >= newItemCount)) mInitialScrollPosition = NO_INITIAL_SCROLL_POSITION; - - if (Global.debugEnabledSql) { - Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onLoadFinished" + - getDebugContext() + - " found " + ((data == null) ? 0 : newItemCount) + " rows"); - } - - // do change the data - mAdapter.notifyDataSetChanged(); - mViewPager.setAdapter(mAdapter); - - // show the changes - onLoadCompleted(); - } - - /** called by LoaderManager. after search criteria were changed or if activity is destroyed. */ - @Override - public void onLoaderReset(Loader loader) { - // rember position where we have to scroll to after refreshLocal is finished. - mInitialScrollPosition = mViewPager.getCurrentItem(); - mAdapter.swapCursor(null); - if (Global.debugEnabledSql) { - Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onLoaderReset" + - getDebugContext()); - } - mAdapter.notifyDataSetChanged(); - } - - @NonNull - private String getDebugContext() { - return "(#" + mRequeryInstanceCount - + ", mScrollPosition=" + mInitialScrollPosition + - ", Path='" + mInitialFilePath + - "')"; - } - } + long mUpdateId = FotoSql.getMediaDBApi().getCurrentUpdateId(); class LocalFileCommands extends AndroidFileCommands { @Override @@ -272,73 +197,172 @@ protected void onPostProcess(String what, int opCode, SelectedFiles selectedFile } - public static class MoveOrCopyDestDirPicker extends DirectoryPickerFragment { - protected static AndroidFileCommands sFileCommands = null; + @Override + protected void onResume() { + unhideActionBar(Global.actionBarHideTimeInMilliSecs, "onResume"); + Global.debugMemory(mDebugPrefix, "onResume"); + super.onResume(); - public static MoveOrCopyDestDirPicker newInstance(boolean move, final SelectedFiles srcFotos) { - MoveOrCopyDestDirPicker f = new MoveOrCopyDestDirPicker(); + final boolean locked = LockScreen.isLocked(this); + if (this.locked != locked) { + this.locked = locked; + mMustReplaceMenue = true; + invalidateOptionsMenu(); + } - // Supply index input as an argument. - Bundle args = new Bundle(); - args.putBoolean("move", move); - AffUtils.putSelectedFiles(args, srcFotos); - f.setArguments(args); + if (Global.debugEnabledMemory) { + Log.d(Global.LOG_CONTEXT, mDebugPrefix + " - onResume cmd (" + + MoveOrCopyDestDirPicker.sFileCommands + ") => (" + mFileCommands + + ")"); - return f; } - /* do not use activity callback */ - @Override - protected void setDirectoryListener(Activity activity) {/* do not use activity callback */} + // workaround fragment lifecycle is newFragment.attach oldFragment.detach. + // this makes shure that the visible fragment has commands + MoveOrCopyDestDirPicker.sFileCommands = mFileCommands; + refreshIfNecessary(); + } - public boolean getMove() { - return getArguments().getBoolean("move", false); - } + @Override + public boolean onOptionsItemSelected(MenuItem menuItem) { + boolean reloadContext = true; + boolean result = true; + boolean slideShowStarted = mSlideShowStarted; - public SelectedFiles getSrcFotos() { - return AffUtils.getSelectedFiles(getArguments()); + onGuiTouched(); + if (LockScreen.onOptionsItemSelected(this, menuItem)) { + mMustReplaceMenue = true; + this.invalidateOptionsMenu(); + return true; } + if (mFileCommands.onOptionsItemSelected(menuItem, getCurrentFoto())) { + mModifyCount++; + } else { + // Handle presses on the action bar items + switch (menuItem.getItemId()) { + case R.id.action_details: + cmdShowDetails(getCurrentFilePath(), getCurrentImageId()); + break; - /** - * To be overwritten to check if a path can be picked. - * - * @param path to be checked if it cannot be handled - * @return null if no error else error message with the reason why it cannot be selected - */ - @Override - protected String getStatusErrorMessage(String path) { - String errorMessage = (sFileCommands == null) ? null : sFileCommands.checkWriteProtected(0, new File(path)); - if (errorMessage != null) { - int pos = errorMessage.indexOf('\n'); - return (pos > 0) ? errorMessage.substring(0,pos) : errorMessage; - } - return super.getStatusErrorMessage(path); - } + case R.id.action_view_context_mode: + pickContextDefinition(); + break; + case R.id.cmd_filemanager: + FileManagerUtil.showInFilemanager(this, getCurrentDir()); + break; - @Override - protected void onDirectoryPick(IDirectory selection) { - // super.onDirectoryPick(selection); - mModifyCount++; // copy or move initiated - getActivity().setResult((mModifyCount == 0) ? RESULT_NOCHANGE : RESULT_CHANGE); - sFileCommands.onMoveOrCopyDirectoryPick(getMove(), getSrcFotos(), selection); - dismiss(); - } - } + case R.id.action_slideshow: + reloadContext = false; + // only if not started + if (!slideShowStarted) startStopSlideShow(true); + break; - private class TagUpdateTask extends TagTask> { + case R.id.action_edit: + // #64: (not) open editor via chooser + IntentUtil.cmdStartIntent("edit", this, getCurrentFilePath(), null, null, + Intent.ACTION_EDIT, + (Global.showEditChooser) ? R.string.edit_chooser_title : 0, + R.string.edit_err_editor_not_found, ACTION_RESULT_MUST_MEDIA_SCAN); + break; - TagUpdateTask(SelectedFiles fotos) { - super(ImageDetailActivityViewPager.this,R.string.tags_activity_title); - this.getWorkflow().init(ImageDetailActivityViewPager.this, fotos, null); + case R.id.menu_item_share: + reloadContext = false; + IntentUtil.cmdStartIntent("share", this, null, null, getCurrentFilePath(), Intent.ACTION_SEND, R.string.share_menu_title, R.string.share_err_not_found, 0); + break; + + case R.id.cmd_copy: + result = cmdMoveOrCopyWithDestDirPicker(false, mFileCommands.getLastCopyToPath(), getCurrentFoto()); + break; + case R.id.cmd_move: + result = cmdMoveOrCopyWithDestDirPicker(true, mFileCommands.getLastCopyToPath(), getCurrentFoto()); + break; + case R.id.menu_item_rename: + result = onRenameQueston(getCurrentFoto(), getCurrentImageId(), getCurrentFilePath(), null); + break; + case R.id.menu_exif: + result = onEditExif(menuItem, getCurrentFoto(), getCurrentImageId(), getCurrentFilePath()); + break; + + case R.id.cmd_gallery: { + reloadContext = false; + String dirPath = getCurrentFilePath(); // PhotoPropertiesMediaFilesScanner.getDir().getAbsolutePath(); + if (dirPath != null) { + dirPath = FileUtils.getDir(dirPath).getAbsolutePath(); + GalleryFilterParameter newFilter = new GalleryFilterParameter(); + newFilter.setPath(dirPath); + // int callBackId = (PhotoPropertiesMediaFilesScanner.isNoMedia(dirPath,PhotoPropertiesMediaFilesScanner.DEFAULT_SCAN_DEPTH)) ? NOMEDIA_GALLERY : 0; + + QueryParameter query = TagSql.filter2NewQuery(this.mFilter); + FotoGalleryActivity.showActivity(" menu " + menuItem.getTitle() + "[13]" + dirPath, + this, query, 0); + } + break; + } + + case R.id.cmd_show_geo: + MapGeoPickerActivity.showActivity(" menu " + menuItem.getTitle(), + this, getCurrentFoto(), null, null, 0); + break; + + case R.id.cmd_show_geo_as: { + final long imageId = getCurrentImageId(); + IGeoPoint _geo = FotoSql.execGetPosition(null, + null, imageId, mDebugPrefix, "on cmd_show_geo_as"); + final String currentFilePath = getCurrentFilePath(); + GeoPointDto geo = new GeoPointDto(_geo.getLatitude(), _geo.getLongitude(), GeoPointDto.NO_ZOOM); + + geo.setDescription(currentFilePath); + geo.setId("" + imageId); + geo.setName("#" + imageId); + GeoUri PARSER = new GeoUri(GeoUri.OPT_PARSE_INFER_MISSING); + String uri = PARSER.toUriString(geo); + + IntentUtil.cmdStartIntent("cmd_show_geo_as", this, null, uri, null, Intent.ACTION_VIEW, R.string.geo_show_as_menu_title, R.string.geo_picker_err_not_found, 0); + break; + } + + case R.id.cmd_edit_geo: { + SelectedFiles selectedItem = getCurrentFoto(); + GeoEditActivity.showActivity(" menu " + menuItem.getTitle() + " " + selectedItem, + this, selectedItem, GeoEditActivity.RESULT_ID); + break; + } + case R.id.cmd_edit_tags: { + SelectedFiles selectedItem = getCurrentFoto(); + tagsShowEditDialog(selectedItem); + break; + } + + case R.id.cmd_about: + reloadContext = false; + AboutDialogPreference.createAboutDialog(this).show(); + break; + case R.id.cmd_settings: + reloadContext = false; + SettingsActivity.showActivity(this); + break; + case R.id.cmd_more: + reloadContext = false; + new Handler().postDelayed(new Runnable() { + public void run() { + // reopen after some delay + openOptionsMenu(); + } + }, 200); + break; + default: + result = super.onOptionsItemSelected(menuItem); + } } - @Override - protected Integer doInBackground(List... params) { - return getWorkflow().updateTags(params[0], params[1]); + if (reloadContext) { + setContextMode(menuItem.getTitle()); } + return result; + } /** @@ -658,29 +682,24 @@ protected void onPause () { super.onPause(); } - @Override - protected void onResume () { - unhideActionBar(Global.actionBarHideTimeInMilliSecs, "onResume"); - Global.debugMemory(mDebugPrefix, "onResume"); - super.onResume(); - - final boolean locked = LockScreen.isLocked(this); - if (this.locked != locked) { - this.locked = locked; - mMustReplaceMenue = true; - invalidateOptionsMenu(); - } + private boolean onRenameQueston(final SelectedFiles currentFoto, final long fotoId, final String fotoPath, final String _newName) { + if (AndroidFileCommands.canProcessFile(this, false)) { + final String newName = (_newName == null) + ? new File(getCurrentFilePath()).getName() + : _newName; - if (Global.debugEnabledMemory) { - Log.d(Global.LOG_CONTEXT, mDebugPrefix + " - onResume cmd (" + - MoveOrCopyDestDirPicker.sFileCommands + ") => (" + mFileCommands + - ")"); + Dialogs dialog = new Dialogs() { + @Override + protected void onDialogResult(String newFileName, Object... parameters) { + if (newFileName != null) { + onRenameAnswer(currentFoto, (Long) parameters[0], (String) parameters[1], newFileName); + } + } + }; + dialog.editFileName(this, getString(R.string.rename_menu_title), newName, fotoId, fotoPath); } - - // workaround fragment lifecycle is newFragment.attach oldFragment.detach. - // this makes shure that the visible fragment has commands - MoveOrCopyDestDirPicker.sFileCommands = mFileCommands; + return true; } @Override @@ -760,16 +779,6 @@ private void onLoadCompleted() { } } - private void refreshIfNecessary() { - if ((mAdapter != null) && (mViewPager != null) && (mAdapter.isInArrayMode())) { - mAdapter.refreshLocal(); - mViewPager.setAdapter(mAdapter); - - // show the changes - onLoadCompleted(); - } - } - /** * gets called if no file is found by a db-query or if jpgFullFilePath is not found in media db * return false; activity must me closed @@ -889,146 +898,43 @@ public boolean onPrepareOptionsMenu(Menu menu) { return super.onPrepareOptionsMenu(menu); } - @Override - public boolean onOptionsItemSelected(MenuItem menuItem) { - boolean reloadContext = true; - boolean result = true; - boolean slideShowStarted = mSlideShowStarted; - - onGuiTouched(); - if (LockScreen.onOptionsItemSelected(this, menuItem)) { - mMustReplaceMenue = true; - this.invalidateOptionsMenu(); - return true; - } - if (mFileCommands.onOptionsItemSelected(menuItem, getCurrentFoto())) { - mModifyCount++; - } else { - // Handle presses on the action bar items - switch (menuItem.getItemId()) { - case R.id.action_details: - cmdShowDetails(getCurrentFilePath(), getCurrentImageId()); - break; - - case R.id.action_view_context_mode: - pickContextDefinition(); - break; - case R.id.cmd_filemanager: - FileManagerUtil.showInFilemanager(this, getCurrentDir()); - break; - - - case R.id.action_slideshow: - reloadContext = false; - // only if not started - if (!slideShowStarted) startStopSlideShow(true); - break; - - case R.id.action_edit: - // #64: (not) open editor via chooser - IntentUtil.cmdStartIntent("edit", this, getCurrentFilePath(), null, null, - Intent.ACTION_EDIT, - (Global.showEditChooser) ? R.string.edit_chooser_title : 0, - R.string.edit_err_editor_not_found, ACTION_RESULT_MUST_MEDIA_SCAN); - break; - - case R.id.menu_item_share: - reloadContext = false; - IntentUtil.cmdStartIntent("share", this, null, null, getCurrentFilePath(), Intent.ACTION_SEND, R.string.share_menu_title, R.string.share_err_not_found, 0); - break; - - case R.id.cmd_copy: - result = cmdMoveOrCopyWithDestDirPicker(false, mFileCommands.getLastCopyToPath(), getCurrentFoto()); - break; - case R.id.cmd_move: - result = cmdMoveOrCopyWithDestDirPicker(true, mFileCommands.getLastCopyToPath(), getCurrentFoto()); - break; - case R.id.menu_item_rename: - result = onRenameDirQueston(getCurrentFoto(), getCurrentImageId(), getCurrentFilePath(), null); - break; - case R.id.menu_exif: - result = onEditExif(menuItem, getCurrentFoto(), getCurrentImageId(), getCurrentFilePath()); - break; - - case R.id.cmd_gallery: { - reloadContext = false; - String dirPath = getCurrentFilePath(); // PhotoPropertiesMediaFilesScanner.getDir().getAbsolutePath(); - if (dirPath != null) { - dirPath = FileUtils.getDir(dirPath).getAbsolutePath(); - GalleryFilterParameter newFilter = new GalleryFilterParameter(); - newFilter.setPath(dirPath); - // int callBackId = (PhotoPropertiesMediaFilesScanner.isNoMedia(dirPath,PhotoPropertiesMediaFilesScanner.DEFAULT_SCAN_DEPTH)) ? NOMEDIA_GALLERY : 0; - - QueryParameter query = TagSql.filter2NewQuery(this.mFilter); - FotoGalleryActivity.showActivity(" menu " + menuItem.getTitle() + "[13]" + dirPath, - this, query, 0); - } - break; - } - - case R.id.cmd_show_geo: - MapGeoPickerActivity.showActivity(" menu " + menuItem.getTitle(), - this, getCurrentFoto(), null, null, 0); - break; - - case R.id.cmd_show_geo_as: { - final long imageId = getCurrentImageId(); - IGeoPoint _geo = FotoSql.execGetPosition(null, - null, imageId, mDebugPrefix, "on cmd_show_geo_as"); - final String currentFilePath = getCurrentFilePath(); - GeoPointDto geo = new GeoPointDto(_geo.getLatitude(), _geo.getLongitude(), GeoPointDto.NO_ZOOM); - - geo.setDescription(currentFilePath); - geo.setId(""+imageId); - geo.setName("#"+imageId); - GeoUri PARSER = new GeoUri(GeoUri.OPT_PARSE_INFER_MISSING); - String uri = PARSER.toUriString(geo); - - IntentUtil.cmdStartIntent("cmd_show_geo_as", this, null, uri, null, Intent.ACTION_VIEW, R.string.geo_show_as_menu_title, R.string.geo_picker_err_not_found, 0); - break; - } + private void onRenameAnswer(SelectedFiles currentFoto, final long fotoId, final String fotoSourcePath, String newFileName) { + File src = new File(fotoSourcePath); + File dest = new File(src.getParentFile(), newFileName); - case R.id.cmd_edit_geo: { - SelectedFiles selectedItem = getCurrentFoto(); - GeoEditActivity.showActivity(" menu " + menuItem.getTitle() + " " + selectedItem, - this, selectedItem, GeoEditActivity.RESULT_ID); - break; - } - case R.id.cmd_edit_tags: { - SelectedFiles selectedItem = getCurrentFoto(); - tagsShowEditDialog(selectedItem); - break; - } + File srcXmpShort = FileProcessor.getSidecar(src, false); + boolean hasSideCarShort = ((srcXmpShort != null) && (mFileCommands.osFileExists(srcXmpShort))); + File srcXmpLong = FileProcessor.getSidecar(src, true); + boolean hasSideCarLong = ((srcXmpLong != null) && (mFileCommands.osFileExists(srcXmpLong))); - case R.id.cmd_about: - reloadContext = false; - AboutDialogPreference.createAboutDialog(this).show(); - break; - case R.id.cmd_settings: - reloadContext = false; - SettingsActivity.showActivity(this); - break; - case R.id.cmd_more: - reloadContext = false; - new Handler().postDelayed(new Runnable() { - public void run() { - // reopen after some delay - openOptionsMenu(); - } - }, 200); - break; + File destXmpShort = FileProcessor.getSidecar(dest, false); + File destXmpLong = FileProcessor.getSidecar(dest, true); - default: - result = super.onOptionsItemSelected(menuItem); - } - } + if (src.equals(dest)) return; // new name == old name ==> nothing to do - if (reloadContext) { - setContextMode(menuItem.getTitle()); + String errorMessage = null; + if (hasSideCarShort && mFileCommands.osFileExists(destXmpShort)) { + errorMessage = getString(R.string.image_err_file_exists_format, destXmpShort.getAbsoluteFile()); + } + if (hasSideCarLong && mFileCommands.osFileExists(destXmpLong)) { + errorMessage = getString(R.string.image_err_file_exists_format, destXmpLong.getAbsoluteFile()); + } + if (mFileCommands.osFileExists(dest)) { + errorMessage = getString(R.string.image_err_file_exists_format, dest.getAbsoluteFile()); } - return result; - + if (errorMessage != null) { + // dest-file already exists + Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show(); + onRenameQueston(currentFoto, fotoId, fotoSourcePath, newFileName); + } else if (mFileCommands.rename(currentFoto, dest, null)) { + mModifyCount++; + refreshIfNecessary(); + } else { + // rename failed + errorMessage = getString(R.string.image_err_file_rename_format, src.getAbsoluteFile()); + Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show(); + } } private static final int SLIDESHOW_HANDLER_ID = 2; @@ -1099,61 +1005,43 @@ private boolean onEditExif(MenuItem menuItem, SelectedFiles currentFoto, final l this, null, fotoPath, currentFoto, 0, true); return true; } - private boolean onRenameDirQueston(final SelectedFiles currentFoto, final long fotoId, final String fotoPath, final String _newName) { - if (AndroidFileCommands.canProcessFile(this, false)) { - final String newName = (_newName == null) - ? new File(getCurrentFilePath()).getName() - : _newName; + /** + * #70: Gui has changed ContextExpression + * + * @param modeName property name. if starting with "auto" then the image detail quick-botton is redefined. + * @param contextSqlColumnExpression if not empy the result of this expression is shown as context data in image detail view. + */ + private void onDefineContext(String modeName, String contextSqlColumnExpression) { + addContextColumn(mGalleryContentQuery, contextSqlColumnExpression); + if ((mGalleryContentQuery != null) + && (0 != StringUtils.compare(contextSqlColumnExpression, mContextColumnExpression)) + && (this.mAdapter != null)) { + // sql detail expression has changed and initialization has completed: requery - Dialogs dialog = new Dialogs() { - @Override - protected void onDialogResult(String newFileName, Object... parameters) { - if (newFileName != null) { - onRenameSubDirAnswer(currentFoto, (Long) parameters[0], (String) parameters[1], newFileName); - } - } - }; - dialog.editFileName(this, getString(R.string.rename_menu_title), newName, fotoId, fotoPath); + this.mContextColumnExpression = contextSqlColumnExpression; // prevent executing again + startRequery(); } - return true; - } - - private void onRenameSubDirAnswer(SelectedFiles currentFoto, final long fotoId, final String fotoSourcePath, String newFileName) { - File src = new File(fotoSourcePath); - File dest = new File(src.getParentFile(), newFileName); - - File srcXmpShort = FileProcessor.getSidecar(src, false); - boolean hasSideCarShort = ((srcXmpShort != null) && (mFileCommands.osFileExists(srcXmpShort))); - File srcXmpLong = FileProcessor.getSidecar(src, true); - boolean hasSideCarLong = ((srcXmpLong != null) && (mFileCommands.osFileExists(srcXmpLong))); + this.mContextColumnExpression = contextSqlColumnExpression; // prevent executing again - File destXmpShort = FileProcessor.getSidecar(dest, false); - File destXmpLong = FileProcessor.getSidecar(dest, true); + if (modeName != null) { + this.mContextName = modeName; + if (this.mAdapter != null) { + this.mAdapter.setIconResourceName(modeName); + } + } - if (src.equals(dest)) return; // new name == old name ==> nothing to do + } - String errorMessage = null; - if (hasSideCarShort && mFileCommands.osFileExists(destXmpShort)) { - errorMessage = getString(R.string.image_err_file_exists_format, destXmpShort.getAbsoluteFile()); - } - if (hasSideCarLong && mFileCommands.osFileExists(destXmpLong)) { - errorMessage = getString(R.string.image_err_file_exists_format, destXmpLong.getAbsoluteFile()); - } - if (mFileCommands.osFileExists(dest)) { - errorMessage = getString(R.string.image_err_file_exists_format, dest.getAbsoluteFile()); - } + private void refreshIfNecessary() { + if ((mAdapter != null) && (mViewPager != null) && (mAdapter.isInArrayMode())) { + mAdapter.refreshLocal(); + mViewPager.setAdapter(mAdapter); - if (errorMessage != null) { - // dest-file already exists - Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show(); - onRenameDirQueston(currentFoto, fotoId, fotoSourcePath, newFileName); - } else if (mFileCommands.rename(currentFoto, dest, null)) { - mModifyCount++; - } else { - // rename failed - errorMessage = getString(R.string.image_err_file_rename_format, src.getAbsoluteFile()); - Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show(); + // show the changes + onLoadCompleted(); + } else if (FotoSql.getMediaDBApi().mustRequery(mUpdateId)) { + startRequery(); } } @@ -1297,38 +1185,177 @@ private void setContextMode(Object modeName) { } } + private void startRequery() { + mUpdateId = FotoSql.getMediaDBApi().getCurrentUpdateId(); + if (mCurorLoader == null) { + // query has not been initialized + mCurorLoader = new LocalCursorLoader(); + getLoaderManager().initLoader(ACTIVITY_ID, null, mCurorLoader); + } else { + // query has changed + getLoaderManager().restartLoader(ACTIVITY_ID, null, this.mCurorLoader); + } + } + + public static class MoveOrCopyDestDirPicker extends DirectoryPickerFragment { + protected static AndroidFileCommands sFileCommands = null; + + public static MoveOrCopyDestDirPicker newInstance(boolean move, final SelectedFiles srcFotos) { + MoveOrCopyDestDirPicker f = new MoveOrCopyDestDirPicker(); + + // Supply index input as an argument. + Bundle args = new Bundle(); + args.putBoolean("move", move); + AffUtils.putSelectedFiles(args, srcFotos); + f.setArguments(args); + + return f; + } + + /* do not use activity callback */ + @Override + protected void setDirectoryListener(Activity activity) {/* do not use activity callback */} + + public boolean getMove() { + return getArguments().getBoolean("move", false); + } + + public SelectedFiles getSrcFotos() { + return AffUtils.getSelectedFiles(getArguments()); + } + + /** + * To be overwritten to check if a path can be picked. + * + * @param path to be checked if it cannot be handled + * @return null if no error else error message with the reason why it cannot be selected + */ + @Override + protected String getStatusErrorMessage(String path) { + String errorMessage = (sFileCommands == null) ? null : sFileCommands.checkWriteProtected(0, new File(path)); + if (errorMessage != null) { + int pos = errorMessage.indexOf('\n'); + return (pos > 0) ? errorMessage.substring(0, pos) : errorMessage; + } + return super.getStatusErrorMessage(path); + } + + @Override + protected void onDirectoryPick(IDirectory selection) { + // super.onDirectoryPick(selection); + mModifyCount++; // copy or move initiated + getActivity().setResult((mModifyCount == 0) ? RESULT_NOCHANGE : RESULT_CHANGE); + + sFileCommands.onMoveOrCopyDirectoryPick(getMove(), getSrcFotos(), selection); + dismiss(); + ((ImageDetailActivityViewPager) getActivity()).refreshIfNecessary(); + } + } + /** - * #70: Gui has changed ContextExpression - * @param modeName property name. if starting with "auto" then the image detail quick-botton is redefined. - * @param contextSqlColumnExpression if not empy the result of this expression is shown as context data in image detail view. + * executes sql to load image detail data in a background task that may survive + * conriguration change (i.e. device rotation) */ - private void onDefineContext(String modeName, String contextSqlColumnExpression) { - addContextColumn(mGalleryContentQuery, contextSqlColumnExpression); - if ((mGalleryContentQuery != null) - && (0 != StringUtils.compare(contextSqlColumnExpression, mContextColumnExpression)) - && (this.mAdapter != null)) { - // sql detail expression has changed and initialization has completed: requery + class LocalCursorLoader implements LoaderManager.LoaderCallbacks { + /** incremented every time a new curster/query is generated */ + private int mRequeryInstanceCount = 0; - this.mContextColumnExpression = contextSqlColumnExpression; // prevent executing again - if (mCurorLoader == null) { - // query has not been initialized - mCurorLoader = new LocalCursorLoader(); - getLoaderManager().initLoader(ACTIVITY_ID, null, mCurorLoader); - } else { - // query has changed - getLoaderManager().restartLoader(ACTIVITY_ID, null, this.mCurorLoader); + /** called by LoaderManager.getLoader(ACTIVITY_ID) to (re)create loader + * that attaches to last query/cursor if it still exist i.e. after rotation */ + @Override + public Loader onCreateLoader(int loaderID, Bundle bundle) { + switch (loaderID) { + case ACTIVITY_ID: + mRequeryInstanceCount++; + mWaitingForMediaScannerResult = false; + if (Global.debugEnabledSql) { + Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onCreateLoader" + + getDebugContext() + + " : query = " + mGalleryContentQuery); + } + return FotoSql.createCursorLoader(getApplicationContext(), mGalleryContentQuery); + default: + // An invalid id was passed in + return null; } } - this.mContextColumnExpression = contextSqlColumnExpression; // prevent executing again - if (modeName != null) { - this.mContextName = modeName; - if (this.mAdapter != null) { - this.mAdapter.setIconResourceName(modeName); + /** called after media db content has changed */ + @Override + public void onLoadFinished(Loader loader, Cursor data) { + // to be restored after refreshLocal if there is no mInitialFilePath + if ((mInitialScrollPosition == NO_INITIAL_SCROLL_POSITION) && (mViewPager != null)) { + mInitialScrollPosition = mViewPager.getCurrentItem(); + } + // do change the data + mUpdateId = FotoSql.getMediaDBApi().getCurrentUpdateId(); + mAdapter.swapCursor(data); + // currentUpdateId + + // restore position is invalid + final int newItemCount = mAdapter.getCount(); + + if (((newItemCount == 0)) || (mInitialScrollPosition >= newItemCount)) + mInitialScrollPosition = NO_INITIAL_SCROLL_POSITION; + + if (Global.debugEnabledSql) { + Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onLoadFinished" + + getDebugContext() + + " found " + ((data == null) ? 0 : newItemCount) + " rows"); + } + + // do change the data + mAdapter.notifyDataSetChanged(); + mViewPager.setAdapter(mAdapter); + + // show the changes + onLoadCompleted(); + } + + /** + * called by LoaderManager. after search criteria were changed or if activity is destroyed. + */ + @Override + public void onLoaderReset(Loader loader) { + // rember position where we have to scroll to after refreshLocal is finished. + mInitialScrollPosition = mViewPager.getCurrentItem(); + mAdapter.swapCursor(null); + if (Global.debugEnabledSql) { + Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onLoaderReset" + + getDebugContext()); } + mAdapter.notifyDataSetChanged(); + } + + @NonNull + private String getDebugContext() { + return "(#" + mRequeryInstanceCount + + ", mScrollPosition=" + mInitialScrollPosition + + ", Path='" + mInitialFilePath + + "')"; + } + } + + private class TagUpdateTask extends TagTask> { + + TagUpdateTask(SelectedFiles fotos) { + super(ImageDetailActivityViewPager.this, R.string.tags_activity_title); + this.getWorkflow().init(ImageDetailActivityViewPager.this, fotos, null); + } + @Override + protected Integer doInBackground(List... params) { + return getWorkflow().updateTags(params[0], params[1]); + } + + @Override + protected void onPostExecute(Integer itemCount) { + super.onPostExecute(itemCount); + refreshIfNecessary(); + } } + /** #70: adds/removes/replases contextColumnExpression */ private static void addContextColumn(QueryParameter query, String contextColumnExpression) { if (query != null) { diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/IMediaDBApi.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/IMediaDBApi.java index 172a6f2c..8a9f67ed 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/IMediaDBApi.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/IMediaDBApi.java @@ -63,4 +63,14 @@ Long insertOrUpdateMediaDatabase(String dbgContext, int deleteMedia(String dbgContext, String where, String[] selectionArgs, boolean preventDeleteImageFile); ContentValues getDbContent(long id); + + long getCurrentUpdateId(); + + boolean mustRequery(long updateId); + + void beginTransaction(); + + void setTransactionSuccessful(); + + void endTransaction(); } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBApiWrapper.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBApiWrapper.java index 39469a24..29743daf 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBApiWrapper.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBApiWrapper.java @@ -35,6 +35,7 @@ public class MediaDBApiWrapper implements IMediaDBApi { protected final IMediaDBApi readChild; protected final IMediaDBApi writeChild; + protected final IMediaDBApi transactionChild; /** * count the non path write calls @@ -42,12 +43,13 @@ public class MediaDBApiWrapper implements IMediaDBApi { private int modifyCount = 0; public MediaDBApiWrapper(IMediaDBApi child) { - this(child, child); + this(child, child, child); } - public MediaDBApiWrapper(IMediaDBApi readChild, IMediaDBApi writeChild) { + public MediaDBApiWrapper(IMediaDBApi readChild, IMediaDBApi writeChild, IMediaDBApi transactionChild) { this.readChild = readChild; this.writeChild = writeChild; + this.transactionChild = transactionChild; } @Override @@ -112,4 +114,29 @@ public int deleteMedia(String dbgContext, String where, String[] selectionArgs, public ContentValues getDbContent(long id) { return readChild.getDbContent(id); } + + @Override + public long getCurrentUpdateId() { + return transactionChild.getCurrentUpdateId(); + } + + @Override + public boolean mustRequery(long updateId) { + return transactionChild.mustRequery(updateId); + } + + @Override + public void beginTransaction() { + transactionChild.beginTransaction(); + } + + @Override + public void setTransactionSuccessful() { + transactionChild.setTransactionSuccessful(); + } + + @Override + public void endTransaction() { + transactionChild.endTransaction(); + } } 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 1e80da62..6285254d 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 @@ -103,4 +103,30 @@ public int deleteMedia(String dbgContext, String where, String[] selectionArgs, public ContentValues getDbContent(final long id) { return ContentProviderMediaImpl.getDbContent(context, id); } + + @Override + public long getCurrentUpdateId() { + return 0; + } + + @Override + public boolean mustRequery(long updateId) { + return false; + } + + @Override + public void beginTransaction() { + + } + + @Override + public void setTransactionSuccessful() { + + } + + @Override + public void endTransaction() { + + } + } 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 82f2a260..2463b4d5 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 @@ -67,6 +67,9 @@ public class MediaImageDbReplacement implements IMediaDBApi { public static final String LOG_TAG = FotoSql.LOG_TAG + "DB"; + // #155 + public static final boolean debugEnabledSqlRefresh = true; + private static final String MODUL_NAME = ContentProviderMediaImpl.class.getName(); private final SQLiteDatabase db; @@ -147,25 +150,7 @@ public int execUpdate(String dbgContext, String path, ContentValues values, VISI return exexUpdateImpl(dbgContext, values, FotoSql.getFilterExprPathLikeWithVisibility(visibility), new String[]{path}); } - @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(LOG_TAG, 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 String currentUpdateReason = null; /** * return id of inserted item @@ -194,6 +179,52 @@ public Long insertOrUpdateMediaDatabase(String dbgContext, String dbUpdateFilter return result; } + private static long currentUpdateId = 1; + + @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); + if (result != 0) { + currentUpdateId++; + currentUpdateReason = dbgContext; + } + } catch (Exception ex) { + excpetion = ex; + } finally { + if ((excpetion != null) || ((dbgContext != null) && (Global.debugEnabledSql || LibGlobal.debugEnabledJpg))) { + Log.i(LOG_TAG, 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; + } + + @Override + public ContentValues getDbContent(long id) { + Cursor c = null; + try { + 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(LOG_TAG, MODUL_NAME + + ".getDbContent(id=" + id + ") failed", ex); + } finally { + if (c != null) c.close(); + } + return null; + } + /** * every database insert should go through this. adds logging if enabled * @@ -207,6 +238,11 @@ public Uri execInsert(String dbgContext, ContentValues values) { 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); + if (result > 0) { + currentUpdateId++; + currentUpdateReason = dbgContext; + } + } catch (Exception ex) { excpetion = ex; } finally { @@ -265,6 +301,11 @@ public int deleteMedia(String dbgContext, String where, String[] selectionArgs, lastUsedWhereClause, lastSelectionArgs, null, delCount)); } } + if (delCount > 0) { + currentUpdateId++; + currentUpdateReason = dbgContext; + } + } catch (Exception ex) { // null pointer exception when delete matches not items?? final String msg = dbgContext + ": Exception in " + @@ -280,25 +321,33 @@ public int deleteMedia(String dbgContext, String where, String[] selectionArgs, } @Override - public ContentValues getDbContent(long id) { - Cursor c = null; - try { - 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(LOG_TAG, MODUL_NAME + - ".getDbContent(id=" + id + ") failed", ex); - } finally { - if (c != null) c.close(); + public long getCurrentUpdateId() { + return currentUpdateId; + } + + @Override + public boolean mustRequery(long updateId) { + final boolean modified = currentUpdateId != updateId; + if (modified && MediaImageDbReplacement.debugEnabledSqlRefresh) { + Log.i(MediaImageDbReplacement.LOG_TAG, "mustRequery: true because of " + currentUpdateReason); } - return null; + return modified; + } + + @Override + public void beginTransaction() { + db.beginTransaction(); } + @Override + public void setTransactionSuccessful() { + db.setTransactionSuccessful(); + } + + @Override + public void endTransaction() { + db.endTransaction(); + } public static class Impl { /** * SQL to create copy of contentprovider MediaStore.Images. diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MergedMediaDB.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MergedMediaDB.java index 0939a6a5..88733f90 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MergedMediaDB.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MergedMediaDB.java @@ -35,7 +35,7 @@ public class MergedMediaDB extends MediaDBApiWrapper { private final IMediaDBApi contentProvider; public MergedMediaDB(IMediaDBApi database, IMediaDBApi contentProvider) { - super(database, contentProvider); + super(database, contentProvider, database); this.database = database; this.contentProvider = contentProvider; } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagWorflow.java b/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagWorflow.java index 5e5bd9ea..475b0e3d 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagWorflow.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagWorflow.java @@ -29,6 +29,8 @@ import java.util.List; import de.k3b.android.androFotoFinder.Global; +import de.k3b.android.androFotoFinder.queries.FotoSql; +import de.k3b.android.androFotoFinder.queries.IMediaDBApi; import de.k3b.android.util.AndroidFileCommands; import de.k3b.io.FileCommands; import de.k3b.io.IProgessListener; @@ -78,21 +80,27 @@ public TagWorflow init(Activity context, SelectedFiles selectedItems, List /** execute the updates for all affected files in the Workflow. */ public int updateTags(List addedTags, List removedTags) { - int itemCount = 0; - if (items != null) { - int progressCountDown = 0; - int total = items.size(); - for (TagSql.TagWorflowItem item : items) { - itemCount+=updateTags(item, addedTags, removedTags); - progressCountDown--; - if (progressCountDown < 0) { - progressCountDown = 10; - if (!onProgress(itemCount, total, item.path)) break; - } - } // for each image + final IMediaDBApi mediaDBApi = FotoSql.getMediaDBApi(); + try { + mediaDBApi.beginTransaction(); // Performance boost: all db-inserts/updates in one transaction + int itemCount = 0; + if (items != null) { + int progressCountDown = 0; + int total = items.size(); + for (TagSql.TagWorflowItem item : items) { + itemCount += updateTags(item, addedTags, removedTags); + progressCountDown--; + if (progressCountDown < 0) { + progressCountDown = 10; + if (!onProgress(itemCount, total, item.path)) break; + } + } // for each image + } + mediaDBApi.setTransactionSuccessful(); + return itemCount; + } finally { + mediaDBApi.endTransaction(); } - - return itemCount; } /** update one file if tags change or xmp does not exist yet: xmp-sidecar-file, media-db and batch */ diff --git a/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java b/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java index 5ac78acb..dfc5f634 100644 --- a/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java +++ b/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java @@ -44,6 +44,7 @@ import de.k3b.android.androFotoFinder.media.AndroidPhotoPropertiesBulkUpdateService; import de.k3b.android.androFotoFinder.queries.DatabaseHelper; import de.k3b.android.androFotoFinder.queries.FotoSql; +import de.k3b.android.androFotoFinder.queries.IMediaDBApi; import de.k3b.android.androFotoFinder.tagDB.TagSql; import de.k3b.android.androFotoFinder.transactionlog.TransactionLogSql; import de.k3b.database.QueryParameter; @@ -55,6 +56,7 @@ import de.k3b.io.collections.SelectedFiles; import de.k3b.media.MediaFormatter; import de.k3b.media.PhotoPropertiesBulkUpdateService; +import de.k3b.media.PhotoPropertiesDiffCopy; import de.k3b.media.PhotoPropertiesUpdateHandler; import de.k3b.transactionlog.MediaTransactionLogEntryType; import de.k3b.transactionlog.TransactionLoggerBase; @@ -252,6 +254,21 @@ public void onMoveOrCopyDirectoryPick(boolean move, SelectedFiles selectedFiles, } } + @Override + protected int moveOrCopyFiles(final boolean move, String what, PhotoPropertiesDiffCopy exifChanges, + SelectedFiles fotos, File[] destFiles, + IProgessListener progessListener) { + IMediaDBApi api = FotoSql.getMediaDBApi(); + try { + api.beginTransaction(); + int result = super.moveOrCopyFiles(move, what, exifChanges, fotos, destFiles, progessListener); + api.setTransactionSuccessful(); + return result; + } finally { + api.endTransaction(); + } + } + @NonNull public String getLastCopyToPath() { SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(mContext); diff --git a/app/src/main/java/de/k3b/android/widget/LocalizedActivity.java b/app/src/main/java/de/k3b/android/widget/LocalizedActivity.java index a2b5e696..6ee9b8fb 100644 --- a/app/src/main/java/de/k3b/android/widget/LocalizedActivity.java +++ b/app/src/main/java/de/k3b/android/widget/LocalizedActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2018 by k3b. + * Copyright (c) 2015-2019 by k3b. * * This file is part of AndroFotoFinder and of ToGoZip. * @@ -41,26 +41,28 @@ * Created by k3b on 07.01.2016. */ public abstract class LocalizedActivity extends ActivityWithCallContext { + /** + * if this.recreationId != LocalizedActivity.currentRecreationId : activity must be recreated in on resume + */ + private static int currentRecreationId = 0; + private int recreationId = 0; + /** if myLocale != Locale.Default : activity must be recreated in on resume */ private Locale myLocale = null; + /** + * All activities will be recreated in on resume. I.E. after basic configuration change. + */ + public static void setMustRecreate() { + LocalizedActivity.currentRecreationId++; + } + @Override protected void onCreate(Bundle savedInstanceState) { fixLocale(this); super.onCreate(savedInstanceState); } - @Override - protected void onResume() { - super.onResume(); - - // Locale has changed by other Activity ? - if ((myLocale != null) && (myLocale.getLanguage() != Locale.getDefault().getLanguage())) { - myLocale = null; - recreate(LocalizedActivity.this); - } - } - /** * Set Activity-s locale to SharedPreferences-setting. * Must be called before @@ -85,11 +87,29 @@ public static void fixLocale(Context context) { // recreate(); if (context instanceof LocalizedActivity) { - ((LocalizedActivity) context).myLocale = locale; + final LocalizedActivity localizedActivity = (LocalizedActivity) context; + localizedActivity.myLocale = locale; + localizedActivity.recreationId = LocalizedActivity.currentRecreationId; } } } + @Override + protected void onResume() { + super.onResume(); + + // Locale has changed by other Activity ? + if (mustRecreate()) { + myLocale = null; + recreate(LocalizedActivity.this); + } + } + + protected boolean mustRecreate() { + return ((this.recreationId != LocalizedActivity.currentRecreationId) || + (this.myLocale != null) && (this.myLocale.getLanguage() != Locale.getDefault().getLanguage())); + } + /** force all open activity to recreate */ public static void recreate(Activity child) { Activity context = child; From ca57d2dcbb49933feeadafe659f4ace46998c1d6 Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Fri, 3 Jan 2020 19:38:54 +0100 Subject: [PATCH 32/37] #155: Gallery refresh query after change after exif edit --- .../AndroidTransactionLogger.java | 6 +- .../gallery/cursor/GalleryCursorFragment.java | 423 ++++++++++-------- .../ImageDetailActivityViewPager.java | 294 ++++++------ .../queries/MediaImageDbReplacement.java | 14 +- .../k3b/android/util/AndroidFileCommands.java | 23 +- .../k3b/android/util/DataChangeNotifyer.java | 33 ++ .../PhotoPropertiesMediaFilesScanner.java | 40 +- ...oPropertiesMediaFilesScannerAsyncTask.java | 34 +- .../de/k3b/android/widget/UpdateTask.java | 44 +- .../src/main/java/de/k3b/io/FileCommands.java | 8 +- 10 files changed, 534 insertions(+), 385 deletions(-) create mode 100644 app/src/main/java/de/k3b/android/util/DataChangeNotifyer.java diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/AndroidTransactionLogger.java b/app/src/main/java/de/k3b/android/androFotoFinder/AndroidTransactionLogger.java index bdc68c6f..a06087ed 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/AndroidTransactionLogger.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/AndroidTransactionLogger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 by k3b. + * Copyright (c) 2017-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -19,8 +19,6 @@ package de.k3b.android.androFotoFinder; -import android.app.Activity; - import java.io.Closeable; import java.io.IOException; @@ -37,7 +35,7 @@ public class AndroidTransactionLogger extends TransactionLoggerBase implements Closeable { private AndroidFileCommands execLog; - public AndroidTransactionLogger(Activity ctx, long now, AndroidFileCommands execLog) { + public AndroidTransactionLogger(AndroidFileCommands execLog, long now) { super(execLog, now); this.execLog = execLog; 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 02696b3f..0d490aca 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 by k3b. + * Copyright (c) 2015-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -82,6 +82,7 @@ import de.k3b.android.androFotoFinder.tagDB.TagsPickerFragment; import de.k3b.android.util.AndroidFileCommands; import de.k3b.android.util.DBUtils; +import de.k3b.android.util.DataChangeNotifyer; import de.k3b.android.util.OsUtils; import de.k3b.android.util.PhotoPropertiesMediaFilesScanner; import de.k3b.android.util.ResourceUtils; @@ -119,7 +120,8 @@ * if (isMultiSelectionActive()) menu_gallery_multiselect_mode_all + menu_image_commands * if (view-non-select) menu_gallery_non_selected_only + menu_gallery_non_multiselect */ -public class GalleryCursorFragment extends Fragment implements Queryable, DirectoryGui,Common, TagsPickerFragment.ITagsPicker { +public class GalleryCursorFragment extends Fragment implements Queryable, DirectoryGui, Common, + TagsPickerFragment.ITagsPicker, DataChangeNotifyer.DataChangedListener { private static final String INSTANCE_STATE_LAST_VISIBLE_POSITION = "lastVisiblePosition"; private static final String INSTANCE_STATE_SELECTED_ITEM_IDS = "selectedItems"; private static final String INSTANCE_STATE_OLD_TITLE = "oldTitle"; @@ -149,6 +151,8 @@ public class GalleryCursorFragment extends Fragment implements Queryable, Direc private GridView mGalleryView; private ShareActionProvider mShareActionProvider; + long mUpdateId = FotoSql.getMediaDBApi().getCurrentUpdateId(); + private GalleryCursorAdapterFromArray mAdapter = null; private OnGalleryInteractionListener mGalleryListener; @@ -205,127 +209,57 @@ public SelectedItems getSelectedItems() { return mSelectedItems; } - class LocalCursorLoader implements LoaderManager.LoaderCallbacks { - /** called by LoaderManager.getLoader(ACTIVITY_ID) to (re)create loader - * that attaches to last query/cursor if it still exist i.e. after rotation */ - @Override - public Loader onCreateLoader(int aLoaderID, Bundle bundle) { - if (loaderID == aLoaderID) { - QueryParameter query = getCurrentQuery(); - mRequeryInstanceCount++; - if (Global.debugEnabledSql) { - Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onCreateLoader" - + getDebugContext() + - " : query = " + query); - } - return FotoSql.createCursorLoader(getActivity().getApplicationContext(), query); - } - - // An invalid id was passed in - return null; - } - - /** called after media db content has changed */ - @Override - public void onLoadFinished(Loader _loader, Cursor data) { - mLastVisiblePosition = mGalleryView.getLastVisiblePosition(); - - final Activity context = getActivity(); - if (data == null) { - 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) { - if (0 != loader.getQuery().toSqlString().compareTo(getCurrentQuery(FotoSql.queryDetail).toSqlString())) { - // query is not default query. revert to default query - mGalleryContentQuery = FotoSql.queryDetail; - requery("requery after query-errror"); - title = context.getString(R.string.global_err_sql_title_reload); - } else { - title = context.getString(R.string.global_err_system); - context.finish(); - } - Dialogs.messagebox(context, title, message); - return; - } - } - - // do change the data - mAdapter.swapCursor(data); - - if (mLastVisiblePosition > 0) { - mGalleryView.smoothScrollToPosition(mLastVisiblePosition); - mLastVisiblePosition = -1; - } - - final int resultCount = (data == null) ? 0 : data.getCount(); - if (Global.debugEnabled) { - Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onLoadFinished" - + getDebugContext() + - " fount " + resultCount + " rows"); - } - - // do change the data - mAdapter.notifyDataSetChanged(); + public void onNotifyDataChanged() { + requeryIfDataHasChanged(); + } - if (mLastVisiblePosition > 0) { - mGalleryView.smoothScrollToPosition(mLastVisiblePosition); - mLastVisiblePosition = -1; - } + /** incremented every time a new curster/query is generated */ + private int mRequeryInstanceCount = 0; - // show the changes + protected LocalCursorLoader mCurorLoader = null; - if (context instanceof OnGalleryInteractionListener) { - ((OnGalleryInteractionListener) context).setResultCount(resultCount); - } - multiSelectionReplaceTitleIfNecessary(); + private void requeryIfDataHasChanged() { + if (FotoSql.getMediaDBApi().mustRequery(mUpdateId)) { + requery("requeryIfDataHasChanged"); } + } - /** called by LoaderManager. after search criteria were changed or if activity is destroyed. */ - @Override - public void onLoaderReset(Loader loader) { - if (Global.debugEnabled) { - Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onLoaderReset" + getDebugContext()); - } - // rember position where we have to scroll to after refreshLocal is finished. - mLastVisiblePosition = mGalleryView.getLastVisiblePosition(); - - mAdapter.swapCursor(null); - mAdapter.notifyDataSetChanged(); + private void requery(String why) { + mUpdateId = FotoSql.getMediaDBApi().getCurrentUpdateId(); + if (Global.debugEnabled) { + Log.i(Global.LOG_CONTEXT, mDebugPrefix + why + " requery\n" + ((mGalleryContentQuery != null) ? mGalleryContentQuery.toSqlString() : null)); } - @NonNull - protected String getDebugContext() { - return "(@" + loaderID + ", #" + mRequeryInstanceCount + - ", LastVisiblePosition=" + mLastVisiblePosition + -// ", Path='" + mInitialFilePath + - "')"; + if (mGalleryContentQuery != null) { + // query has been initialized + if (mCurorLoader == null) { + mCurorLoader = new LocalCursorLoader(); + getLoaderManager().initLoader(loaderID, null, mCurorLoader); + } else { + getLoaderManager().restartLoader(loaderID, null, this.mCurorLoader); + } } } - /** incremented every time a new curster/query is generated */ - private int mRequeryInstanceCount = 0; - - protected LocalCursorLoader mCurorLoader = null; - - protected class LocalFileCommands extends AndroidFileCommands { + private boolean cmdMoveOrCopyWithDestDirPicker(final boolean move, String lastCopyToPath, final SelectedFiles fotos) { + if (AndroidFileCommands.canProcessFile(this.getActivity(), false)) { + DataChangeNotifyer.setDataChangedListener(this); + mDestDirPicker = MoveOrCopyDestDirPicker.newInstance(move, fotos); - @Override - protected void onPostProcess(String what, int opCode, SelectedFiles selectedFiles, int modifyCount, int itemCount, String[] oldPathNames, String[] newPathNames) { - if (Global.clearSelectionAfterCommand || (opCode == OP_DELETE) || (opCode == OP_MOVE)) { - mShowSelectedOnly = true; - multiSelectionCancel(); + mDestDirPicker.defineDirectoryNavigation(OsUtils.getRootOSDirectory(null), + (move) ? FotoSql.QUERY_TYPE_GROUP_MOVE : FotoSql.QUERY_TYPE_GROUP_COPY, + lastCopyToPath); + if (!LockScreen.isLocked(this.getActivity())) { + mDestDirPicker.setContextMenuId(R.menu.menu_context_pick_osdir); } - super.onPostProcess(what, opCode, selectedFiles, modifyCount, itemCount, oldPathNames, newPathNames); - - if ((mAdapter.isInArrayMode()) && ((opCode == OP_RENAME) || (opCode == OP_MOVE) || (opCode == OP_DELETE))) { - mAdapter.refreshLocal(); - mGalleryView.setAdapter(mAdapter); - } + mDestDirPicker.setBaseQuery(getCurrentQuery()); + mDestDirPicker.show(getActivity().getFragmentManager(), "osdir"); } + return false; } + public GalleryCursorFragment() { mDebugPrefix = "GalleryCursorFragment#" + (id++) + " "; Global.debugMemory(mDebugPrefix, "ctor"); @@ -599,20 +533,31 @@ public void requery(Activity context, QueryParameter parameters, String why) { requery(why); } - private void requery(String why) { - if (Global.debugEnabled) { - Log.i(Global.LOG_CONTEXT, mDebugPrefix + why + " requery\n" + ((mGalleryContentQuery != null) ? mGalleryContentQuery.toSqlString() : null)); - } + /** + * image entries may not have DISPLAY_NAME which is essential for calculating the item-s folder. + */ + private void repairMissingDisplayNames() { + SqlJobTaskBase task = new SqlJobTaskBase(this.getActivity(), "Searching media database for missing 'displayname'-s:\n", null) { + private int mPathColNo = -2; + private int mResultCount = 0; - if (mGalleryContentQuery != null) { - // query has been initialized - if (mCurorLoader == null) { - mCurorLoader = new LocalCursorLoader(); - getLoaderManager().initLoader(loaderID, null, mCurorLoader); - } else { - getLoaderManager().restartLoader(loaderID, null, this.mCurorLoader); + @Override + protected void doInBackground(Long id, Cursor cursor) { + if (mPathColNo == -2) mPathColNo = cursor.getColumnIndex(FotoSql.SQL_COL_PATH); + mResultCount += PhotoPropertiesMediaFilesScanner.getInstance(getActivity()).updatePathRelatedFields(getActivity(), cursor, cursor.getString(mPathColNo), mColumnIndexPK, mPathColNo); } - } + + @Override + protected void onPostExecute(SelectedItems selectedItems) { + if (!isCancelled()) { + onMissingDisplayNamesComplete(mStatus); + onNotifyDataChanged(); + } + } + }; + QueryParameter query = FotoSql.queryGetMissingDisplayNames; + FotoSql.setWhereVisibility(query, VISIBILITY.PRIVATE_PUBLIC); + task.execute(query); } private QueryParameter getCurrentQuery() { @@ -966,19 +911,43 @@ private void cmdShowDetails() { // setAutoClose(null, dlg, null); } - private class TagUpdateTask extends TagTask> { - - TagUpdateTask(SelectedFiles fotos) { - super(GalleryCursorFragment.this.getActivity(),R.string.tags_activity_title); - this.getWorkflow().init(GalleryCursorFragment.this.getActivity(), fotos, null); - - } + private void removeDuplicates() { + SqlJobTaskBase task = new SqlJobTaskBase(this.getActivity(), "Searching for duplcates in media database:\n", null) { + @Override + protected void doInBackground(Long id, Cursor cursor) { + this.mSelectedItems.add(id); + if (mStatus != null) { + mStatus + .append("\nduplicate found ") + .append(id) + .append("#") + .append(DBUtils.getString(cursor, FotoSql.SQL_COL_DISPLAY_TEXT, "???")) + //.append("\n") + ; + } + } - @Override - protected Integer doInBackground(List... params) { - return getWorkflow().updateTags(params[0], params[1]); - } + @Override + protected void onPostExecute(SelectedItems selectedItems) { + if (!isCancelled()) { + if ((selectedItems != null) && (selectedItems.size() > 0)) { + onDuplicatesFound(selectedItems, mStatus); + } else { + onDuplicatesFound(null, mStatus); + } + onNotifyDataChanged(); + } else { + if (mStatus != null) { + mStatus.append("\nTask canceled"); + Log.w(Global.LOG_CONTEXT, mDebugPrefix + mStatus); + } + } + } + }; + QueryParameter query = FotoSql.queryGetDuplicates; + FotoSql.setWhereVisibility(query, VISIBILITY.PRIVATE_PUBLIC); + task.execute(query); } private boolean onEditExif(MenuItem menuItem, SelectedFiles fotos) { @@ -1070,21 +1039,109 @@ protected void onDirectoryPick(IDirectory selection) { } } - private boolean cmdMoveOrCopyWithDestDirPicker(final boolean move, String lastCopyToPath, final SelectedFiles fotos) { - if (AndroidFileCommands.canProcessFile(this.getActivity(), false)) { - mDestDirPicker = MoveOrCopyDestDirPicker.newInstance(move, fotos); + class LocalCursorLoader implements LoaderManager.LoaderCallbacks { + /** + * called by LoaderManager.getLoader(ACTIVITY_ID) to (re)create loader + * that attaches to last query/cursor if it still exist i.e. after rotation + */ + @Override + public Loader onCreateLoader(int aLoaderID, Bundle bundle) { + if (loaderID == aLoaderID) { + QueryParameter query = getCurrentQuery(); + mRequeryInstanceCount++; + if (Global.debugEnabledSql) { + Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onCreateLoader" + + getDebugContext() + + " : query = " + query); + } + return FotoSql.createCursorLoader(getActivity().getApplicationContext(), query); + } - mDestDirPicker.defineDirectoryNavigation(OsUtils.getRootOSDirectory(null), - (move) ? FotoSql.QUERY_TYPE_GROUP_MOVE : FotoSql.QUERY_TYPE_GROUP_COPY, - lastCopyToPath); - if (!LockScreen.isLocked(this.getActivity())) { - mDestDirPicker.setContextMenuId(R.menu.menu_context_pick_osdir); + // An invalid id was passed in + return null; + } + + /** + * called after media db content has changed + */ + @Override + public void onLoadFinished(Loader _loader, Cursor data) { + mLastVisiblePosition = mGalleryView.getLastVisiblePosition(); + + final Activity context = getActivity(); + if (data == null) { + 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) { + if (0 != loader.getQuery().toSqlString().compareTo(getCurrentQuery(FotoSql.queryDetail).toSqlString())) { + // query is not default query. revert to default query + mGalleryContentQuery = FotoSql.queryDetail; + requery("requery after query-errror"); + title = context.getString(R.string.global_err_sql_title_reload); + } else { + title = context.getString(R.string.global_err_system); + context.finish(); + } + Dialogs.messagebox(context, title, message); + return; + } } - mDestDirPicker.setBaseQuery(getCurrentQuery()); - mDestDirPicker.show(getActivity().getFragmentManager(), "osdir"); + mUpdateId = FotoSql.getMediaDBApi().getCurrentUpdateId(); + // do change the data + mAdapter.swapCursor(data); + + if (mLastVisiblePosition > 0) { + mGalleryView.smoothScrollToPosition(mLastVisiblePosition); + mLastVisiblePosition = -1; + } + + final int resultCount = (data == null) ? 0 : data.getCount(); + if (Global.debugEnabled) { + Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onLoadFinished" + + getDebugContext() + + " fount " + resultCount + " rows"); + } + + // do change the data + mAdapter.notifyDataSetChanged(); + + if (mLastVisiblePosition > 0) { + mGalleryView.smoothScrollToPosition(mLastVisiblePosition); + mLastVisiblePosition = -1; + } + + // show the changes + + if (context instanceof OnGalleryInteractionListener) { + ((OnGalleryInteractionListener) context).setResultCount(resultCount); + } + multiSelectionReplaceTitleIfNecessary(); + } + + /** + * called by LoaderManager. after search criteria were changed or if activity is destroyed. + */ + @Override + public void onLoaderReset(Loader loader) { + if (Global.debugEnabled) { + Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onLoaderReset" + getDebugContext()); + } + // rember position where we have to scroll to after refreshLocal is finished. + mLastVisiblePosition = mGalleryView.getLastVisiblePosition(); + + mAdapter.swapCursor(null); + mAdapter.notifyDataSetChanged(); + } + + @NonNull + protected String getDebugContext() { + return "(@" + loaderID + ", #" + mRequeryInstanceCount + + ", LastVisiblePosition=" + mLastVisiblePosition + +// ", Path='" + mInitialFilePath + + "')"; } - return false; } private boolean onPickOk() { @@ -1330,28 +1387,25 @@ private void fixMediaDatabase() { } } - /** image entries may not have DISPLAY_NAME which is essential for calculating the item-s folder. */ - private void repairMissingDisplayNames() { - SqlJobTaskBase task = new SqlJobTaskBase(this.getActivity(), "Searching media database for missing 'displayname'-s:\n", null) { - private int mPathColNo = -2; - private int mResultCount = 0; + protected class LocalFileCommands extends AndroidFileCommands { - @Override - protected void doInBackground(Long id, Cursor cursor) { - if (mPathColNo == -2) mPathColNo = cursor.getColumnIndex(FotoSql.SQL_COL_PATH); - mResultCount += PhotoPropertiesMediaFilesScanner.getInstance(getActivity()).updatePathRelatedFields(getActivity(), cursor, cursor.getString(mPathColNo), mColumnIndexPK, mPathColNo); + @Override + protected void onPostProcess(String what, int opCode, SelectedFiles selectedFiles, int modifyCount, int itemCount, String[] oldPathNames, String[] newPathNames) { + if (Global.clearSelectionAfterCommand || (opCode == OP_DELETE) || (opCode == OP_MOVE)) { + mShowSelectedOnly = true; + multiSelectionCancel(); } - @Override - protected void onPostExecute(SelectedItems selectedItems) { - if (!isCancelled()) { - onMissingDisplayNamesComplete(mStatus); - } + super.onPostProcess(what, opCode, selectedFiles, modifyCount, itemCount, oldPathNames, newPathNames); + + if ((mAdapter.isInArrayMode()) && ((opCode == OP_RENAME) || (opCode == OP_MOVE) || (opCode == OP_DELETE))) { + mAdapter.refreshLocal(); + mGalleryView.setAdapter(mAdapter); } - }; - QueryParameter query = FotoSql.queryGetMissingDisplayNames; - FotoSql.setWhereVisibility(query, VISIBILITY.PRIVATE_PUBLIC); - task.execute(query); + if ((opCode == OP_RENAME) || (opCode == OP_MOVE) || (opCode == OP_DELETE) || (opCode == OP_RENAME)) { + requeryIfDataHasChanged(); + } + } } /** called after MissingDisplayNamesComplete finished */ @@ -1361,42 +1415,25 @@ private void onMissingDisplayNamesComplete(StringBuffer debugMessage) { } } - private void removeDuplicates() { - SqlJobTaskBase task = new SqlJobTaskBase(this.getActivity(), "Searching for duplcates in media database:\n", null) { - @Override - protected void doInBackground(Long id, Cursor cursor) { - this.mSelectedItems.add(id); - if (mStatus != null) { - mStatus - .append("\nduplicate found ") - .append(id) - .append("#") - .append(DBUtils.getString(cursor,FotoSql.SQL_COL_DISPLAY_TEXT,"???")) - //.append("\n") - ; - } - } + private class TagUpdateTask extends TagTask> { - @Override - protected void onPostExecute(SelectedItems selectedItems) { - if (!isCancelled()) { - if ((selectedItems != null) && (selectedItems.size() > 0)) { - onDuplicatesFound(selectedItems, mStatus); - } else { - onDuplicatesFound(null, mStatus); - } - } else { - if (mStatus != null) { - mStatus.append("\nTask canceled"); - Log.w(Global.LOG_CONTEXT, mDebugPrefix + mStatus); - } + TagUpdateTask(SelectedFiles fotos) { + super(GalleryCursorFragment.this.getActivity(), R.string.tags_activity_title); + this.getWorkflow().init(GalleryCursorFragment.this.getActivity(), fotos, null); + + } + + @Override + protected Integer doInBackground(List... params) { + return getWorkflow().updateTags(params[0], params[1]); + } + + @Override + protected void onPostExecute(Integer itemCount) { + super.onPostExecute(itemCount); + onNotifyDataChanged(); + } - } - } - }; - QueryParameter query = FotoSql.queryGetDuplicates; - FotoSql.setWhereVisibility(query, VISIBILITY.PRIVATE_PUBLIC); - task.execute(query); } /** is called when removeDuplicates() found duplicates */ diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java index 4a8b5f2d..e7f72f3d 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java @@ -1,6 +1,6 @@ /******************************************************************************* * Copyright 2011, 2012 Chris Banes. - * Copyright (c) 2015-2019 by k3b. + * Copyright (c) 2015-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -64,6 +64,7 @@ import de.k3b.android.androFotoFinder.tagDB.TagTask; import de.k3b.android.androFotoFinder.tagDB.TagsPickerFragment; import de.k3b.android.util.AndroidFileCommands; +import de.k3b.android.util.DataChangeNotifyer; import de.k3b.android.util.FileManagerUtil; import de.k3b.android.util.IntentUtil; import de.k3b.android.util.OsUtils; @@ -92,7 +93,8 @@ * Swipe left/right to show previous/next image. */ -public class ImageDetailActivityViewPager extends ActivityWithAutoCloseDialogs implements Common, TagsPickerFragment.ITagsPicker { +public class ImageDetailActivityViewPager extends ActivityWithAutoCloseDialogs implements Common, TagsPickerFragment.ITagsPicker, + DataChangeNotifyer.DataChangedListener { private static final String INSTANCE_STATE_MODIFY_COUNT = "mModifyCount"; private static final String INSTANCE_STATE_LAST_SCROLL_POSITION = "lastScrollPosition"; /** #70: remember on config change (screen rotation) */ @@ -167,36 +169,6 @@ public class ImageDetailActivityViewPager extends ActivityWithAutoCloseDialogs i long mUpdateId = FotoSql.getMediaDBApi().getCurrentUpdateId(); - class LocalFileCommands extends AndroidFileCommands { - @Override - protected void onPostProcess(String what, int opCode, SelectedFiles selectedFiles, int modifyCount, int itemCount, String[] oldPathNames, String[] newPathNames) { - mInitialFilePath = null; - switch (opCode) { - case OP_MOVE: - case OP_RENAME: - if ((newPathNames!= null) && (newPathNames.length > 0)) { - // so selection will be restored to this after load complete - mInitialFilePath = newPathNames[0]; - } - break; - case OP_COPY: - if ((oldPathNames!= null) && (oldPathNames.length > 0)) { - // so selection will be restored to this after load complete - mInitialFilePath = oldPathNames[0]; - } - break; - default:break; - } - - super.onPostProcess(what, opCode, selectedFiles, modifyCount, itemCount, oldPathNames, newPathNames); - - if ((opCode == OP_RENAME) || (opCode == OP_MOVE) || (opCode == OP_DELETE)) { - refreshIfNecessary(); - } - } - - } - @Override protected void onResume() { unhideActionBar(Global.actionBarHideTimeInMilliSecs, "onResume"); @@ -220,7 +192,7 @@ protected void onResume() { // workaround fragment lifecycle is newFragment.attach oldFragment.detach. // this makes shure that the visible fragment has commands MoveOrCopyDestDirPicker.sFileCommands = mFileCommands; - refreshIfNecessary(); + reloadIfDataHasChanged(); } @Override @@ -278,6 +250,7 @@ public boolean onOptionsItemSelected(MenuItem menuItem) { result = cmdMoveOrCopyWithDestDirPicker(true, mFileCommands.getLastCopyToPath(), getCurrentFoto()); break; case R.id.menu_item_rename: + DataChangeNotifyer.setDataChangedListener(this); result = onRenameQueston(getCurrentFoto(), getCurrentImageId(), getCurrentFilePath(), null); break; case R.id.menu_exif: @@ -365,6 +338,36 @@ public void run() { } + @Override + protected void onActivityResult(final int requestCode, + final int resultCode, final Intent intent) { + super.onActivityResult(requestCode, resultCode, intent); + if (mDestDirPicker != null) + mDestDirPicker.onActivityResult(requestCode, resultCode, intent); + + final boolean locked = LockScreen.isLocked(this); + if (this.locked != locked) { + this.locked = locked; + mMustReplaceMenue = true; + invalidateOptionsMenu(); + } + + if (requestCode == ACTION_RESULT_FORWARD) { + // forward result from child-activity to parent-activity + setResult(resultCode, intent); + finish(); + } else if (requestCode == ACTION_RESULT_MUST_MEDIA_SCAN) { + // #64 after edit the content might have been changed. update media DB. + String orgiginalFileToScan = getCurrentFilePath(); + + if (orgiginalFileToScan != null) { + PhotoPropertiesMediaFilesScanner.getInstance(this).updateMediaDatabase_Android42(this, null, orgiginalFileToScan); + } + } + + reloadIfDataHasChanged(); + } + /** * shows a new instance of ImageDetailActivityViewPager. * @@ -559,32 +562,12 @@ private void copyExtras(Intent dest, Bundle source, String... keys) { } @Override - protected void onActivityResult(final int requestCode, - final int resultCode, final Intent intent) { - super.onActivityResult(requestCode, resultCode, intent); - if (mDestDirPicker != null) mDestDirPicker.onActivityResult(requestCode,resultCode,intent); - - final boolean locked = LockScreen.isLocked(this); - if (this.locked != locked) { - this.locked = locked; - mMustReplaceMenue = true; - invalidateOptionsMenu(); - } - - if (requestCode == ACTION_RESULT_FORWARD) { - // forward result from child-activity to parent-activity - setResult(resultCode, intent); - finish(); - } else if (requestCode == ACTION_RESULT_MUST_MEDIA_SCAN) { - // #64 after edit the content might have been changed. update media DB. - String orgiginalFileToScan = getCurrentFilePath(); - - if (orgiginalFileToScan != null) { - PhotoPropertiesMediaFilesScanner.getInstance(this).updateMediaDatabase_Android42(this, null, orgiginalFileToScan); - } - } - - refreshIfNecessary(); + protected void onPause() { + unhideActionBar(DISABLE_HIDE_ACTIONBAR, "onPause"); + Global.debugMemory(mDebugPrefix, "onPause"); + startStopSlideShow(false); + DataChangeNotifyer.setDataChangedListener(null); // notify triggererd in onResume + super.onPause(); } private static boolean mustForward(Intent intent) { @@ -674,12 +657,43 @@ public void setTitle(CharSequence title) { } */ - @Override - protected void onPause () { - unhideActionBar(DISABLE_HIDE_ACTIONBAR, "onPause"); - Global.debugMemory(mDebugPrefix, "onPause"); - startStopSlideShow(false); - super.onPause(); + private void onRenameAnswer(SelectedFiles currentFoto, final long fotoId, final String fotoSourcePath, String newFileName) { + File src = new File(fotoSourcePath); + File dest = new File(src.getParentFile(), newFileName); + + File srcXmpShort = FileProcessor.getSidecar(src, false); + boolean hasSideCarShort = ((srcXmpShort != null) && (mFileCommands.osFileExists(srcXmpShort))); + File srcXmpLong = FileProcessor.getSidecar(src, true); + boolean hasSideCarLong = ((srcXmpLong != null) && (mFileCommands.osFileExists(srcXmpLong))); + + File destXmpShort = FileProcessor.getSidecar(dest, false); + File destXmpLong = FileProcessor.getSidecar(dest, true); + + if (src.equals(dest)) return; // new name == old name ==> nothing to do + + String errorMessage = null; + if (hasSideCarShort && mFileCommands.osFileExists(destXmpShort)) { + errorMessage = getString(R.string.image_err_file_exists_format, destXmpShort.getAbsoluteFile()); + } + if (hasSideCarLong && mFileCommands.osFileExists(destXmpLong)) { + errorMessage = getString(R.string.image_err_file_exists_format, destXmpLong.getAbsoluteFile()); + } + if (mFileCommands.osFileExists(dest)) { + errorMessage = getString(R.string.image_err_file_exists_format, dest.getAbsoluteFile()); + } + + DataChangeNotifyer.setDataChangedListener(this); + if (errorMessage != null) { + // dest-file already exists + Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show(); + onRenameQueston(currentFoto, fotoId, fotoSourcePath, newFileName); + } else if (mFileCommands.rename(currentFoto, dest, null)) { + mModifyCount++; + } else { + // rename failed + errorMessage = getString(R.string.image_err_file_rename_format, src.getAbsoluteFile()); + Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show(); + } } private boolean onRenameQueston(final SelectedFiles currentFoto, final long fotoId, final String fotoPath, final String _newName) { @@ -898,43 +912,19 @@ public boolean onPrepareOptionsMenu(Menu menu) { return super.onPrepareOptionsMenu(menu); } - private void onRenameAnswer(SelectedFiles currentFoto, final long fotoId, final String fotoSourcePath, String newFileName) { - File src = new File(fotoSourcePath); - File dest = new File(src.getParentFile(), newFileName); - - File srcXmpShort = FileProcessor.getSidecar(src, false); - boolean hasSideCarShort = ((srcXmpShort != null) && (mFileCommands.osFileExists(srcXmpShort))); - File srcXmpLong = FileProcessor.getSidecar(src, true); - boolean hasSideCarLong = ((srcXmpLong != null) && (mFileCommands.osFileExists(srcXmpLong))); - - File destXmpShort = FileProcessor.getSidecar(dest, false); - File destXmpLong = FileProcessor.getSidecar(dest, true); - - if (src.equals(dest)) return; // new name == old name ==> nothing to do - - String errorMessage = null; - if (hasSideCarShort && mFileCommands.osFileExists(destXmpShort)) { - errorMessage = getString(R.string.image_err_file_exists_format, destXmpShort.getAbsoluteFile()); - } - if (hasSideCarLong && mFileCommands.osFileExists(destXmpLong)) { - errorMessage = getString(R.string.image_err_file_exists_format, destXmpLong.getAbsoluteFile()); - } - if (mFileCommands.osFileExists(dest)) { - errorMessage = getString(R.string.image_err_file_exists_format, dest.getAbsoluteFile()); - } + private boolean cmdMoveOrCopyWithDestDirPicker(final boolean move, String lastCopyToPath, final SelectedFiles fotos) { + if (AndroidFileCommands.canProcessFile(this, false)) { + DataChangeNotifyer.setDataChangedListener(this); + mDestDirPicker = MoveOrCopyDestDirPicker.newInstance(move, fotos); - if (errorMessage != null) { - // dest-file already exists - Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show(); - onRenameQueston(currentFoto, fotoId, fotoSourcePath, newFileName); - } else if (mFileCommands.rename(currentFoto, dest, null)) { - mModifyCount++; - refreshIfNecessary(); - } else { - // rename failed - errorMessage = getString(R.string.image_err_file_rename_format, src.getAbsoluteFile()); - Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show(); + mDestDirPicker.defineDirectoryNavigation(OsUtils.getRootOSDirectory(null), + (move) ? FotoSql.QUERY_TYPE_GROUP_MOVE : FotoSql.QUERY_TYPE_GROUP_COPY, + lastCopyToPath); + mDestDirPicker.setContextMenuId(LockScreen.isLocked(this) ? 0 : R.menu.menu_context_pick_osdir); + mDestDirPicker.setBaseQuery(mGalleryContentQuery); + mDestDirPicker.show(this.getFragmentManager(), "osdirimage"); } + return false; } private static final int SLIDESHOW_HANDLER_ID = 2; @@ -986,26 +976,6 @@ private void cmdShowDetails(String fullFilePath, long currentImageId) { } - private boolean cmdMoveOrCopyWithDestDirPicker(final boolean move, String lastCopyToPath, final SelectedFiles fotos) { - if (AndroidFileCommands.canProcessFile(this, false)) { - mDestDirPicker = MoveOrCopyDestDirPicker.newInstance(move, fotos); - - mDestDirPicker.defineDirectoryNavigation(OsUtils.getRootOSDirectory(null), - (move) ? FotoSql.QUERY_TYPE_GROUP_MOVE : FotoSql.QUERY_TYPE_GROUP_COPY, - lastCopyToPath); - mDestDirPicker.setContextMenuId(LockScreen.isLocked(this) ? 0 : R.menu.menu_context_pick_osdir); - mDestDirPicker.setBaseQuery(mGalleryContentQuery); - mDestDirPicker.show(this.getFragmentManager(), "osdirimage"); - } - return false; - } - - private boolean onEditExif(MenuItem menuItem, SelectedFiles currentFoto, final long fotoId, final String fotoPath) { - PhotoPropertiesEditActivity.showActivity(" menu " + menuItem.getTitle(), - this, null, fotoPath, currentFoto, 0, true); - return true; - } - /** * #70: Gui has changed ContextExpression * @@ -1020,7 +990,7 @@ private void onDefineContext(String modeName, String contextSqlColumnExpression) // sql detail expression has changed and initialization has completed: requery this.mContextColumnExpression = contextSqlColumnExpression; // prevent executing again - startRequery(); + requery(); } this.mContextColumnExpression = contextSqlColumnExpression; // prevent executing again @@ -1033,15 +1003,31 @@ private void onDefineContext(String modeName, String contextSqlColumnExpression) } - private void refreshIfNecessary() { + private boolean onEditExif(MenuItem menuItem, SelectedFiles currentFoto, final long fotoId, final String fotoPath) { + PhotoPropertiesEditActivity.showActivity(" menu " + menuItem.getTitle(), + this, null, fotoPath, currentFoto, 0, true); + return true; + } + + public void onNotifyDataChanged() { + requeryIfDataHasChanged(); + } + + private void reloadIfDataHasChanged() { if ((mAdapter != null) && (mViewPager != null) && (mAdapter.isInArrayMode())) { mAdapter.refreshLocal(); mViewPager.setAdapter(mAdapter); // show the changes onLoadCompleted(); - } else if (FotoSql.getMediaDBApi().mustRequery(mUpdateId)) { - startRequery(); + } else { + requeryIfDataHasChanged(); + } + } + + private void requeryIfDataHasChanged() { + if (FotoSql.getMediaDBApi().mustRequery(mUpdateId)) { + requery(); } } @@ -1055,10 +1041,23 @@ private boolean tagsShowEditDialog(SelectedFiles fotos) { dlg.setRemoveNames(new ArrayList()); dlg.setBaseQuery(mGalleryContentQuery); setAutoClose(dlg, null, null); + DataChangeNotifyer.setDataChangedListener(this); dlg.show(getFragmentManager(), "editTags"); return true; } + private void requery() { + mUpdateId = FotoSql.getMediaDBApi().getCurrentUpdateId(); + if (mCurorLoader == null) { + // query has not been initialized + mCurorLoader = new LocalCursorLoader(); + getLoaderManager().initLoader(ACTIVITY_ID, null, mCurorLoader); + } else { + // query has changed + getLoaderManager().restartLoader(ACTIVITY_ID, null, this.mCurorLoader); + } + } + /** called by {@link TagsPickerFragment} */ @Override public boolean onCancel(String msg) { @@ -1185,18 +1184,6 @@ private void setContextMode(Object modeName) { } } - private void startRequery() { - mUpdateId = FotoSql.getMediaDBApi().getCurrentUpdateId(); - if (mCurorLoader == null) { - // query has not been initialized - mCurorLoader = new LocalCursorLoader(); - getLoaderManager().initLoader(ACTIVITY_ID, null, mCurorLoader); - } else { - // query has changed - getLoaderManager().restartLoader(ACTIVITY_ID, null, this.mCurorLoader); - } - } - public static class MoveOrCopyDestDirPicker extends DirectoryPickerFragment { protected static AndroidFileCommands sFileCommands = null; @@ -1246,12 +1233,43 @@ protected void onDirectoryPick(IDirectory selection) { mModifyCount++; // copy or move initiated getActivity().setResult((mModifyCount == 0) ? RESULT_NOCHANGE : RESULT_CHANGE); + DataChangeNotifyer.setDataChangedListener(((ImageDetailActivityViewPager) getActivity())); sFileCommands.onMoveOrCopyDirectoryPick(getMove(), getSrcFotos(), selection); dismiss(); - ((ImageDetailActivityViewPager) getActivity()).refreshIfNecessary(); } } + class LocalFileCommands extends AndroidFileCommands { + @Override + protected void onPostProcess(String what, int opCode, SelectedFiles selectedFiles, int modifyCount, int itemCount, String[] oldPathNames, String[] newPathNames) { + mInitialFilePath = null; + switch (opCode) { + case OP_MOVE: + case OP_RENAME: + if ((newPathNames != null) && (newPathNames.length > 0)) { + // so selection will be restored to this after load complete + mInitialFilePath = newPathNames[0]; + } + break; + case OP_COPY: + if ((oldPathNames != null) && (oldPathNames.length > 0)) { + // so selection will be restored to this after load complete + mInitialFilePath = oldPathNames[0]; + } + break; + default: + break; + } + + super.onPostProcess(what, opCode, selectedFiles, modifyCount, itemCount, oldPathNames, newPathNames); + + if ((opCode == OP_RENAME) || (opCode == OP_MOVE) || (opCode == OP_DELETE) || (opCode == OP_RENAME)) { + reloadIfDataHasChanged(); + } + } + + } + /** * executes sql to load image detail data in a background task that may survive * conriguration change (i.e. device rotation) @@ -1352,7 +1370,7 @@ protected Integer doInBackground(List... params) { @Override protected void onPostExecute(Integer itemCount) { super.onPostExecute(itemCount); - refreshIfNecessary(); + reloadIfDataHasChanged(); } } 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 2463b4d5..caa409f5 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 by k3b. + * Copyright (c) 2015-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -334,18 +334,30 @@ public boolean mustRequery(long updateId) { return modified; } + private static int transactionNumber = 0; + @Override public void beginTransaction() { + if (Global.debugEnabledSql) { + Log.i(LOG_TAG, "beginTransaction #" + (++transactionNumber)); + } + db.beginTransaction(); } @Override public void setTransactionSuccessful() { + if (Global.debugEnabledSql) { + Log.i(LOG_TAG, "setTransactionSuccessful #" + transactionNumber); + } db.setTransactionSuccessful(); } @Override public void endTransaction() { + if (Global.debugEnabledSql) { + Log.i(LOG_TAG, "endTransaction #" + transactionNumber); + } db.endTransaction(); } public static class Impl { diff --git a/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java b/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java index dfc5f634..ea7c1f46 100644 --- a/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java +++ b/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 by k3b. + * Copyright (c) 2015-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -37,6 +37,7 @@ import java.io.File; import java.util.Date; +import de.k3b.android.androFotoFinder.AndroidTransactionLogger; import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.LockScreen; import de.k3b.android.androFotoFinder.R; @@ -44,7 +45,6 @@ import de.k3b.android.androFotoFinder.media.AndroidPhotoPropertiesBulkUpdateService; import de.k3b.android.androFotoFinder.queries.DatabaseHelper; import de.k3b.android.androFotoFinder.queries.FotoSql; -import de.k3b.android.androFotoFinder.queries.IMediaDBApi; import de.k3b.android.androFotoFinder.tagDB.TagSql; import de.k3b.android.androFotoFinder.transactionlog.TransactionLogSql; import de.k3b.database.QueryParameter; @@ -258,15 +258,9 @@ public void onMoveOrCopyDirectoryPick(boolean move, SelectedFiles selectedFiles, protected int moveOrCopyFiles(final boolean move, String what, PhotoPropertiesDiffCopy exifChanges, SelectedFiles fotos, File[] destFiles, IProgessListener progessListener) { - IMediaDBApi api = FotoSql.getMediaDBApi(); - try { - api.beginTransaction(); - int result = super.moveOrCopyFiles(move, what, exifChanges, fotos, destFiles, progessListener); - api.setTransactionSuccessful(); - return result; - } finally { - api.endTransaction(); - } + int result = super.moveOrCopyFiles(move, what, exifChanges, fotos, destFiles, progessListener); + // api.setTransactionSuccessful(); + return result; } @NonNull @@ -572,6 +566,10 @@ public PhotoPropertiesBulkUpdateService createWorkflow(TransactionLoggerBase log return new AndroidPhotoPropertiesBulkUpdateService(mContext, logger, dbgContext); } + @Override + protected TransactionLoggerBase createTransactionLogger(long now) { + return new AndroidTransactionLogger(this, now); + } /** adds android database specific logging to base implementation */ @Override @@ -587,6 +585,9 @@ public void addTransactionLog( ContentValues values = TransactionLogSql.set(null, currentMediaID, fileFullPath, modificationDate, mediaTransactionLogEntryType, commandData); db.insert(TransactionLogSql.TABLE, null, values); + if (Global.debugEnabledSql) { + Log.i(FotoSql.LOG_TAG, "addTransactionLog: " + values); + } } } } diff --git a/app/src/main/java/de/k3b/android/util/DataChangeNotifyer.java b/app/src/main/java/de/k3b/android/util/DataChangeNotifyer.java new file mode 100644 index 00000000..54371bcf --- /dev/null +++ b/app/src/main/java/de/k3b/android/util/DataChangeNotifyer.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019-2020 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see + */ + +package de.k3b.android.util; + +public class DataChangeNotifyer { + public static DataChangedListener dataChangedListener = null; + + public static void setDataChangedListener(DataChangedListener dataChangedListener) { + DataChangeNotifyer.dataChangedListener = dataChangedListener; + } + + public interface DataChangedListener { + void onNotifyDataChanged(); + } + +} 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 47c89d18..c4d8c2fe 100644 --- a/app/src/main/java/de/k3b/android/util/PhotoPropertiesMediaFilesScanner.java +++ b/app/src/main/java/de/k3b/android/util/PhotoPropertiesMediaFilesScanner.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 by k3b. + * Copyright (c) 2015-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -45,6 +45,7 @@ import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.media.PhotoPropertiesMediaDBContentValues; import de.k3b.android.androFotoFinder.queries.FotoSql; +import de.k3b.android.androFotoFinder.queries.IMediaDBApi; import de.k3b.android.androFotoFinder.tagDB.TagSql; import de.k3b.database.QueryParameter; import de.k3b.geo.api.GeoPointDto; @@ -159,19 +160,27 @@ public static int hideFolderMedia(Activity context, String path) { } public int updateMediaDatabase_Android42(Context context, String[] oldPathNames, String... newPathNames) { - final boolean hasNew = excludeNomediaFiles(newPathNames) > 0; - final boolean hasOld = excludeNomediaFiles(oldPathNames) > 0; - int result = 0; - - if (hasNew && hasOld) { - result = renameInMediaDatabase(context, oldPathNames, newPathNames); - } else if (hasOld) { - result = deleteInMediaDatabase(context, oldPathNames); - } if (hasNew) { - result = insertIntoMediaDatabase(context, newPathNames); + IMediaDBApi api = FotoSql.getMediaDBApi(); + try { + api.beginTransaction(); + final boolean hasNew = excludeNomediaFiles(newPathNames) > 0; + final boolean hasOld = excludeNomediaFiles(oldPathNames) > 0; + int result = 0; + + if (hasNew && hasOld) { + result = renameInMediaDatabase(context, oldPathNames, newPathNames); + } else if (hasOld) { + result = deleteInMediaDatabase(context, oldPathNames); + } + if (hasNew) { + result = insertIntoMediaDatabase(context, newPathNames); + } + TagSql.fixPrivate(); + api.setTransactionSuccessful(); + return result; + } finally { + api.endTransaction(); } - TagSql.fixPrivate(); - return result; } /** @@ -275,7 +284,10 @@ private int renameInMediaDatabase(Context context, String[] oldPathNames, String String newPathName = newPathNames[i]; if ((oldPathName != null) && (newPathName != null)) { - old2NewFileNames.put(oldPathName, newPathName); + //!!! ?seiteneffekt update other fields? + if (oldPathName.compareToIgnoreCase(newPathName) != 0) { + old2NewFileNames.put(oldPathName, newPathName); + } } else if (oldPathName != null) { deleteFileNames.add(oldPathName); } else if (newPathName != null) { diff --git a/app/src/main/java/de/k3b/android/util/PhotoPropertiesMediaFilesScannerAsyncTask.java b/app/src/main/java/de/k3b/android/util/PhotoPropertiesMediaFilesScannerAsyncTask.java index 2d2439bf..f05c7f91 100644 --- a/app/src/main/java/de/k3b/android/util/PhotoPropertiesMediaFilesScannerAsyncTask.java +++ b/app/src/main/java/de/k3b/android/util/PhotoPropertiesMediaFilesScannerAsyncTask.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019 by k3b. + * Copyright (c) 2016-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -50,17 +50,13 @@ protected Integer doInBackground(String[]... pathNames) { return mScanner.updateMediaDatabase_Android42(mContext, pathNames[0], pathNames[1]); } - @Override - protected void onPostExecute(Integer modifyCount) { - super.onPostExecute(modifyCount); - String message = this.mContext.getString(R.string.scanner_update_result_format, modifyCount); - Toast.makeText(this.mContext, message, Toast.LENGTH_LONG).show(); - if (Global.debugEnabled) { - Log.i(Global.LOG_CONTEXT, CONTEXT + "A42 scanner finished: " + message); - } - + private static void notifyIfThereAreChanges(Integer modifyCount, Context context, String why) { if (modifyCount > 0) { - PhotoPropertiesMediaFilesScanner.notifyChanges(mContext, mWhy); + PhotoPropertiesMediaFilesScanner.notifyChanges(context, why); + if (DataChangeNotifyer.dataChangedListener != null) { + DataChangeNotifyer.dataChangedListener.onNotifyDataChanged(); + } + } } @@ -73,12 +69,22 @@ public static void updateMediaDBInBackground(PhotoPropertiesMediaFilesScanner sc } else { // Continute in background task int modifyCount = scanner.updateMediaDatabase_Android42(context.getApplicationContext(), oldPathNames, newPathNames); - if (modifyCount > 0) { - PhotoPropertiesMediaFilesScanner.notifyChanges(context, why + " within current non-gui-task"); - } + notifyIfThereAreChanges(modifyCount, context, why + " within current non-gui-task"); } } + @Override + protected void onPostExecute(Integer modifyCount) { + super.onPostExecute(modifyCount); + String message = this.mContext.getString(R.string.scanner_update_result_format, modifyCount); + Toast.makeText(this.mContext, message, Toast.LENGTH_LONG).show(); + if (Global.debugEnabled) { + Log.i(Global.LOG_CONTEXT, CONTEXT + "A42 scanner finished: " + message); + } + + notifyIfThereAreChanges(modifyCount, mContext, mWhy); + } + /** return true if this is executed in the gui thread */ private static boolean isGuiThread() { return (Looper.myLooper() == Looper.getMainLooper()); diff --git a/app/src/main/java/de/k3b/android/widget/UpdateTask.java b/app/src/main/java/de/k3b/android/widget/UpdateTask.java index 81aee347..05cea078 100644 --- a/app/src/main/java/de/k3b/android/widget/UpdateTask.java +++ b/app/src/main/java/de/k3b/android/widget/UpdateTask.java @@ -1,14 +1,30 @@ -package de.k3b.android.widget; - -/** - * Created by EVE on 20.11.2017. +/* + * Copyright (c) 2017-2020 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see */ +package de.k3b.android.widget; import android.app.Activity; import android.util.Log; import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.R; +import de.k3b.android.androFotoFinder.queries.FotoSql; +import de.k3b.android.androFotoFinder.queries.IMediaDBApi; import de.k3b.android.util.AndroidFileCommands; import de.k3b.io.IProgessListener; import de.k3b.io.collections.SelectedFiles; @@ -23,7 +39,7 @@ public class UpdateTask extends AsyncTaskWithProgressDialog imple private final AndroidFileCommands cmd; public UpdateTask(Activity ctx, AndroidFileCommands cmd, - PhotoPropertiesDiffCopy exifChanges) { + PhotoPropertiesDiffCopy exifChanges) { super(ctx, R.string.exif_menu_title); this.exifChanges = exifChanges; this.cmd = cmd; @@ -33,13 +49,25 @@ public UpdateTask(Activity ctx, AndroidFileCommands cmd, protected Integer doInBackground(SelectedFiles... params) { publishProgress("..."); + int result = 0; if (exifChanges != null) { SelectedFiles items = params[0]; - return cmd.applyExifChanges(true, exifChanges, items, null); - + if (true) { + result = cmd.applyExifChanges(true, exifChanges, items, null); + } else { + // disabled: does not work because of overlapping transactions + IMediaDBApi api = FotoSql.getMediaDBApi(); + try { + api.beginTransaction(); + result = cmd.applyExifChanges(true, exifChanges, items, null); + api.setTransactionSuccessful(); + } finally { + api.endTransaction(); + } + } } - return 0; + return result; } @Override diff --git a/fotolib2/src/main/java/de/k3b/io/FileCommands.java b/fotolib2/src/main/java/de/k3b/io/FileCommands.java index 2963be8b..9f627ceb 100644 --- a/fotolib2/src/main/java/de/k3b/io/FileCommands.java +++ b/fotolib2/src/main/java/de/k3b/io/FileCommands.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2018 by k3b. + * Copyright (c) 2015-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -274,7 +274,7 @@ protected int moveOrCopyFiles(final boolean move, String what, PhotoPropertiesDi int pos = 0; long now = new Date().getTime(); MediaTransactionLogEntryType moveOrCopyCommand = (move) ? MediaTransactionLogEntryType.MOVE : MediaTransactionLogEntryType.COPY; - TransactionLoggerBase logger = (exifChanges == null) ? null : new TransactionLoggerBase(this, now); + TransactionLoggerBase logger = (exifChanges == null) ? null : createTransactionLogger(now); boolean sameFile; while (pos < fileCount) { @@ -373,6 +373,10 @@ protected int moveOrCopyFiles(final boolean move, String what, PhotoPropertiesDi return itemCount; } + protected TransactionLoggerBase createTransactionLogger(long now) { + return new TransactionLoggerBase(this, now); + } + private PhotoAutoprocessingDto getPhotoAutoprocessingDto(File destDirFolder) { PhotoAutoprocessingDto autoProccessData = null; try { From d0c011a17a374b594430ac59152ad4d0c69a5ad6 Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Fri, 3 Jan 2020 21:53:12 +0100 Subject: [PATCH 33/37] #155: Gallery refresh query after detail-change or delete --- .../gallery/cursor/GalleryCursorFragment.java | 4 ++-- .../imagedetail/ImageDetailActivityViewPager.java | 2 +- .../java/de/k3b/android/util/AndroidFileCommands.java | 10 +++++++--- 3 files changed, 10 insertions(+), 6 deletions(-) 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 0d490aca..e3d5d693 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 @@ -438,7 +438,7 @@ public void onResume() { // workaround fragment lifecycle is newFragment.attach oldFragment.detach. // this makes shure that the visible fragment has commands MoveOrCopyDestDirPicker.sFileCommands = mFileCommands; - + requeryIfDataHasChanged(); } /** @@ -835,7 +835,7 @@ public boolean onOptionsItemSelected(MenuItem menuItem) { AndroidFileCommands fileCommands = mFileCommands; final SelectedFiles selectedFiles = this.mAdapter.createSelectedFiles(getActivity(), this.mSelectedItems); - if ((mSelectedItems != null) && (fileCommands.onOptionsItemSelected(menuItem, selectedFiles))) { + if ((mSelectedItems != null) && (fileCommands.onOptionsItemSelected(menuItem, selectedFiles, this))) { return true; } switch (menuItem.getItemId()) { diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java index e7f72f3d..52553ef0 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java @@ -207,7 +207,7 @@ public boolean onOptionsItemSelected(MenuItem menuItem) { this.invalidateOptionsMenu(); return true; } - if (mFileCommands.onOptionsItemSelected(menuItem, getCurrentFoto())) { + if (mFileCommands.onOptionsItemSelected(menuItem, getCurrentFoto(), this)) { mModifyCount++; } else { // Handle presses on the action bar items diff --git a/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java b/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java index ea7c1f46..8d001f99 100644 --- a/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java +++ b/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java @@ -173,12 +173,12 @@ private static int getResourceId(int opCode) { } - public boolean onOptionsItemSelected(final MenuItem item, final SelectedFiles selectedFileNames) { + public boolean onOptionsItemSelected(final MenuItem item, final SelectedFiles selectedFileNames, DataChangeNotifyer.DataChangedListener dataChangedListener) { if ((selectedFileNames != null) && (selectedFileNames.size() > 0)) { // Handle item selection switch (item.getItemId()) { case R.id.cmd_delete: - return cmdDeleteFileWithQuestion(selectedFileNames); + return cmdDeleteFileWithQuestion(selectedFileNames, dataChangedListener); default:break; } } @@ -276,7 +276,8 @@ private void setLastCopyToPath(String copyToPath) { edit.apply(); } - public boolean cmdDeleteFileWithQuestion(final SelectedFiles fotos) { + public boolean cmdDeleteFileWithQuestion(final SelectedFiles fotos, + final DataChangeNotifyer.DataChangedListener dataChangedListener) { String[] pathNames = fotos.getFileNames(); String errorMessage = checkWriteProtected(R.string.delete_menu_title, SelectedFiles.getFiles(pathNames)); @@ -307,6 +308,9 @@ public void onClick( final int id) { mActiveAlert = null; deleteFiles(fotos, null); + if (dataChangedListener != null) { + dataChangedListener.onNotifyDataChanged(); + } } } ) From 1f3d8f565c3db927f0233424a1ae149cf1f5a7c8 Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Mon, 13 Jan 2020 05:41:35 +0100 Subject: [PATCH 34/37] #155: Refactored: Renamed new classes to *Service/*Repository --- .../androFotoFinder/AndroFotoFinderApp.java | 46 ++++++++++--------- .../androFotoFinder/FotoGalleryActivity.java | 4 +- .../ImageDetailMetaDialogBuilder.java | 6 +-- .../queries/DatabaseHelper.java | 4 +- .../androFotoFinder/queries/FotoSql.java | 8 ++-- ...diaDBApi.java => IMediaRepositoryApi.java} | 6 +-- ...java => MediaContent2DBUpdateService.java} | 34 +++++++++++--- ...va => MediaContentproviderRepository.java} | 22 +++++---- ...> MediaContentproviderRepositoryImpl.java} | 6 +-- ...eplacement.java => MediaDBRepository.java} | 18 ++++---- ...er.java => MediaRepositoryApiWrapper.java} | 16 +++---- ...ediaDB.java => MergedMediaRepository.java} | 10 ++-- .../androFotoFinder/tagDB/TagWorflow.java | 6 +-- .../PhotoPropertiesMediaFilesScanner.java | 4 +- .../de/k3b/android/widget/UpdateTask.java | 4 +- 15 files changed, 110 insertions(+), 84 deletions(-) rename app/src/main/java/de/k3b/android/androFotoFinder/queries/{IMediaDBApi.java => IMediaRepositoryApi.java} (95%) rename app/src/main/java/de/k3b/android/androFotoFinder/queries/{MediaDBUpdater.java => MediaContent2DBUpdateService.java} (64%) rename app/src/main/java/de/k3b/android/androFotoFinder/queries/{MediaDBContentprovider.java => MediaContentproviderRepository.java} (81%) rename app/src/main/java/de/k3b/android/androFotoFinder/queries/{ContentProviderMediaImpl.java => MediaContentproviderRepositoryImpl.java} (98%) rename app/src/main/java/de/k3b/android/androFotoFinder/queries/{MediaImageDbReplacement.java => MediaDBRepository.java} (97%) rename app/src/main/java/de/k3b/android/androFotoFinder/queries/{MediaDBApiWrapper.java => MediaRepositoryApiWrapper.java} (88%) rename app/src/main/java/de/k3b/android/androFotoFinder/queries/{MergedMediaDB.java => MergedMediaRepository.java} (93%) diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java index cd652893..e5047e5e 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 by k3b. + * Copyright (c) 2015-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -38,15 +38,15 @@ import de.k3b.LibGlobal; import de.k3b.android.GuiUtil; import de.k3b.android.androFotoFinder.imagedetail.HugeImageLoader; -import de.k3b.android.androFotoFinder.queries.ContentProviderMediaImpl; import de.k3b.android.androFotoFinder.queries.DatabaseHelper; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.androFotoFinder.queries.FotoSqlBase; -import de.k3b.android.androFotoFinder.queries.IMediaDBApi; -import de.k3b.android.androFotoFinder.queries.MediaDBContentprovider; -import de.k3b.android.androFotoFinder.queries.MediaDBUpdater; -import de.k3b.android.androFotoFinder.queries.MediaImageDbReplacement; -import de.k3b.android.androFotoFinder.queries.MergedMediaDB; +import de.k3b.android.androFotoFinder.queries.IMediaRepositoryApi; +import de.k3b.android.androFotoFinder.queries.MediaContent2DBUpdateService; +import de.k3b.android.androFotoFinder.queries.MediaContentproviderRepository; +import de.k3b.android.androFotoFinder.queries.MediaContentproviderRepositoryImpl; +import de.k3b.android.androFotoFinder.queries.MediaDBRepository; +import de.k3b.android.androFotoFinder.queries.MergedMediaRepository; import de.k3b.android.osmdroid.forge.MapsForgeSupport; import de.k3b.android.util.LogCat; import de.k3b.android.widget.ActivityWithCallContext; @@ -67,10 +67,10 @@ */ public class AndroFotoFinderApp extends Application { private static String fileNamePrefix = "androFotofinder.logcat-"; - private static MediaDBUpdater mediaDbUpdater = null; + private static MediaContent2DBUpdateService mediaContent2DbUpdateService = null; - public static MediaDBUpdater getMediaDbUpdater() { - return mediaDbUpdater; + public static MediaContent2DBUpdateService getMediaContent2DbUpdateService() { + return mediaContent2DbUpdateService; } private LogCat mCrashSaveToFile = null; @@ -92,7 +92,7 @@ public static String getGetTeaserText(Context context, String linkUrlForDetails) } public static void setMediaImageDbReplacement(Context context, boolean useMediaImageDbReplacement) { - final IMediaDBApi oldMediaDBApi = FotoSql.getMediaDBApi(); + final IMediaRepositoryApi oldMediaDBApi = FotoSql.getMediaDBApi(); if ((oldMediaDBApi == null) || (Global.useMediaImageDbReplacement != useMediaImageDbReplacement)) { // menu must be recreated @@ -100,26 +100,28 @@ public static void setMediaImageDbReplacement(Context context, boolean useMediaI Global.useMediaImageDbReplacement = useMediaImageDbReplacement; - final MediaDBContentprovider mediaDBContentprovider = new MediaDBContentprovider(context); + final MediaContentproviderRepository mediaContentproviderRepository = new MediaContentproviderRepository(context); if (Global.useMediaImageDbReplacement) { final SQLiteDatabase writableDatabase = DatabaseHelper.getWritableDatabase(context); - final MediaImageDbReplacement mediaImageDbReplacement = new MediaImageDbReplacement(writableDatabase); - FotoSql.setMediaDBApi(new MergedMediaDB(mediaImageDbReplacement, mediaDBContentprovider)); + final MediaDBRepository mediaDBRepository = new MediaDBRepository(writableDatabase); + FotoSql.setMediaDBApi(new MergedMediaRepository(mediaDBRepository, mediaContentproviderRepository)); - AndroFotoFinderApp.mediaDbUpdater = new MediaDBUpdater(context, writableDatabase); + AndroFotoFinderApp.mediaContent2DbUpdateService = new MediaContent2DBUpdateService(context, writableDatabase); if (FotoSql.getCount(new QueryParameter().addWhere("1 = 1")) == 0) { // database is empty; reload from Contentprovider - AndroFotoFinderApp.mediaDbUpdater.rebuild(context, null); + AndroFotoFinderApp.mediaContent2DbUpdateService.rebuild(context, null); } + + } else { - if ((oldMediaDBApi != null) && (AndroFotoFinderApp.mediaDbUpdater != null)) { + if ((oldMediaDBApi != null) && (AndroFotoFinderApp.mediaContent2DbUpdateService != null)) { // switching from mediaImageDbReplacement to Contentprovider - AndroFotoFinderApp.mediaDbUpdater.clearMediaCopy(); + AndroFotoFinderApp.mediaContent2DbUpdateService.clearMediaCopy(); } - FotoSql.setMediaDBApi(mediaDBContentprovider); - AndroFotoFinderApp.mediaDbUpdater = null; + FotoSql.setMediaDBApi(mediaContentproviderRepository); + AndroFotoFinderApp.mediaContent2DbUpdateService = null; } } } @@ -164,8 +166,8 @@ public static RefWatcher getRefWatcher(Context context) { LibGlobal.LOG_TAG, ThumbNailUtils.LOG_TAG, IMapView.LOGTAG, ExifInterface.LOG_TAG, PhotoPropertiesImageReader.LOG_TAG, FotoSql.LOG_TAG, - MediaImageDbReplacement.LOG_TAG, - ContentProviderMediaImpl.LOG_TAG) { + MediaDBRepository.LOG_TAG, + MediaContentproviderRepositoryImpl.LOG_TAG) { @Override public void uncaughtException(Thread thread, Throwable ex) { 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 883c4402..f14871b1 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-2018 by k3b. + * Copyright (c) 2015-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -208,7 +208,7 @@ public boolean onOptionsItemSelected(MenuItem item) { AboutDialogPreference.createAboutDialog(this).show(); return true; case R.id.cmd_db_reload: - AndroFotoFinderApp.getMediaDbUpdater().rebuild(this, null); + AndroFotoFinderApp.getMediaContent2DbUpdateService().rebuild(this, null); return true; case R.id.cmd_more: new Handler().postDelayed(new Runnable() { diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailMetaDialogBuilder.java b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailMetaDialogBuilder.java index 16e54560..13e7fe8f 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailMetaDialogBuilder.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailMetaDialogBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 by k3b. + * Copyright (c) 2015-2020 by k3b. * * This file is part of AndroFotoFinder. * @@ -33,7 +33,7 @@ import java.util.Date; import java.util.List; -import de.k3b.android.androFotoFinder.queries.ContentProviderMediaImpl; +import de.k3b.android.androFotoFinder.queries.MediaContentproviderRepositoryImpl; import de.k3b.android.androFotoFinder.tagDB.TagSql; import de.k3b.android.widget.ActivityWithCallContext; import de.k3b.database.QueryParameter; @@ -138,7 +138,7 @@ private static void appendExifInfo(StringBuilder result, Activity context, Strin if (currentImageId != 0) { - ContentValues dbContent = ContentProviderMediaImpl.getDbContent(context, currentImageId); + ContentValues dbContent = MediaContentproviderRepositoryImpl.getDbContent(context, currentImageId); if (dbContent != null) { result.append(NL).append(line).append(NL); result.append(NL).append(TagSql.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE).append(NL).append(NL); 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 5a983f79..18c44a81 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 by k3b. + * Copyright (c) 2017-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -52,7 +52,7 @@ public static SQLiteDatabase getWritableDatabase(Context context) { } public static void version2Upgrade_RecreateMediDbCopy(final SQLiteDatabase db) { - for (String sql : MediaImageDbReplacement.Impl.DDL) { + for (String sql : MediaDBRepository.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 dee35020..de044b7c 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 by k3b. + * Copyright (c) 2015-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -279,13 +279,13 @@ public class FotoSql extends FotoSqlBase { */ private static final int SIZE_TRANLATION_LIMIT = SIZE_K * 10; - private static IMediaDBApi mediaDBApi; + private static IMediaRepositoryApi mediaDBApi; - public static IMediaDBApi getMediaDBApi() { + public static IMediaRepositoryApi getMediaDBApi() { return FotoSql.mediaDBApi; } - public static void setMediaDBApi(IMediaDBApi mediaDBApi) { + public static void setMediaDBApi(IMediaRepositoryApi mediaDBApi) { FotoSql.mediaDBApi = mediaDBApi; } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/IMediaDBApi.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/IMediaRepositoryApi.java similarity index 95% rename from app/src/main/java/de/k3b/android/androFotoFinder/queries/IMediaDBApi.java rename to app/src/main/java/de/k3b/android/androFotoFinder/queries/IMediaRepositoryApi.java index 8a9f67ed..b82e1d5f 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/IMediaDBApi.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/IMediaRepositoryApi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 by k3b. + * Copyright (c) 2019-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -27,9 +27,9 @@ import de.k3b.io.VISIBILITY; /** - * media database api + * RepositoryApi for media database access. */ -public interface IMediaDBApi { +public interface IMediaRepositoryApi { Cursor createCursorForQuery( StringBuilder out_debugMessage, String dbgContext, QueryParameter parameters, VISIBILITY visibility, CancellationSignal cancellationSignal); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBUpdater.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaContent2DBUpdateService.java similarity index 64% rename from app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBUpdater.java rename to app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaContent2DBUpdateService.java index 146eded6..bcee5576 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBUpdater.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaContent2DBUpdateService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 by k3b. + * Copyright (c) 2019-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -19,33 +19,53 @@ package de.k3b.android.androFotoFinder.queries; import android.content.Context; +import android.database.ContentObserver; import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; import android.widget.Toast; import java.util.Date; import de.k3b.io.IProgessListener; -public class MediaDBUpdater { +/** + * #155: takes care that chages from + * {@link MediaContentproviderRepository} are transfered to {@link MediaDBRepository} + */ +public class MediaContent2DBUpdateService { private final Context context; private final SQLiteDatabase writableDatabase; - public MediaDBUpdater(Context context, SQLiteDatabase writableDatabase) { + // called when image-/file-mediacontent has changed to indicate that data must + // be loaded from content-provider to content-copy + private static final ContentObserver mMediaObserverDirectory = new ContentObserver(null) { + + // ignore version with 3rd param: int userId + @Override + public void onChange(boolean selfChange, Uri uri) { + super.onChange(selfChange, uri); + + } + }; + + public MediaContent2DBUpdateService(Context context, SQLiteDatabase writableDatabase) { this.context = context; this.writableDatabase = writableDatabase; } + public void clearMediaCopy() { + DatabaseHelper.version2Upgrade_RecreateMediDbCopy(writableDatabase); + } + public void rebuild(Context context, IProgessListener progessListener) { long start = new Date().getTime(); clearMediaCopy(); - MediaImageDbReplacement.Impl.updateMedaiCopy(context, writableDatabase, null, progessListener); + MediaDBRepository.Impl.updateMedaiCopy(context, writableDatabase, 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 clearMediaCopy() { - DatabaseHelper.version2Upgrade_RecreateMediDbCopy(writableDatabase); - } + } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBContentprovider.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaContentproviderRepository.java similarity index 81% rename from app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBContentprovider.java rename to app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaContentproviderRepository.java index 6285254d..f599c1d4 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBContentprovider.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaContentproviderRepository.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 by k3b. + * Copyright (c) 2019-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -28,12 +28,14 @@ import de.k3b.io.VISIBILITY; /** + * Access Media Data through Android media contentprovider. + * * Implementation of Context.getContentResolver()-ContentProvider based media api */ -public class MediaDBContentprovider implements IMediaDBApi { +public class MediaContentproviderRepository implements IMediaRepositoryApi { private final Context context; - public MediaDBContentprovider(final Context context) { + public MediaContentproviderRepository(final Context context) { this.context = context; } @@ -41,7 +43,7 @@ public MediaDBContentprovider(final Context context) { public Cursor createCursorForQuery( StringBuilder out_debugMessage, String dbgContext, QueryParameter parameters, VISIBILITY visibility, CancellationSignal cancellationSignal) { - return ContentProviderMediaImpl.createCursorForQuery( + return MediaContentproviderRepositoryImpl.createCursorForQuery( out_debugMessage, dbgContext, context, parameters, visibility, cancellationSignal); } @@ -49,7 +51,7 @@ public Cursor createCursorForQuery( public Cursor createCursorForQuery(StringBuilder out_debugMessage, String dbgContext, final String from, final String sqlWhereStatement, final String[] sqlWhereParameters, final String sqlSortOrder, CancellationSignal cancellationSignal, final String... sqlSelectColums) { - return ContentProviderMediaImpl.createCursorForQuery( + return MediaContentproviderRepositoryImpl.createCursorForQuery( out_debugMessage, dbgContext, context, from, sqlWhereStatement, sqlWhereParameters, sqlSortOrder, null, sqlSelectColums); } @@ -66,7 +68,7 @@ public int execUpdate(String dbgContext, String path, ContentValues values, VISI @Override public int exexUpdateImpl(String dbgContext, ContentValues values, String sqlWhere, String[] selectionArgs) { - return ContentProviderMediaImpl.exexUpdateImpl(dbgContext, context, values, sqlWhere, selectionArgs); + return MediaContentproviderRepositoryImpl.exexUpdateImpl(dbgContext, context, values, sqlWhere, selectionArgs); } /** @@ -77,7 +79,7 @@ public Long insertOrUpdateMediaDatabase(String dbgContext, String dbUpdateFilterJpgFullPathName, ContentValues values, VISIBILITY visibility, Long updateSuccessValue) { - return ContentProviderMediaImpl.insertOrUpdateMediaDatabase(dbgContext, context, + return MediaContentproviderRepositoryImpl.insertOrUpdateMediaDatabase(dbgContext, context, dbUpdateFilterJpgFullPathName, values, visibility, updateSuccessValue); @@ -88,7 +90,7 @@ public Long insertOrUpdateMediaDatabase(String dbgContext, */ @Override public Uri execInsert(String dbgContext, ContentValues values) { - return ContentProviderMediaImpl.execInsert(dbgContext, context, values); + return MediaContentproviderRepositoryImpl.execInsert(dbgContext, context, values); } /** @@ -96,12 +98,12 @@ public Uri execInsert(String dbgContext, ContentValues values) { */ @Override public int deleteMedia(String dbgContext, String where, String[] selectionArgs, boolean preventDeleteImageFile) { - return ContentProviderMediaImpl.deleteMedia(dbgContext, context, where, selectionArgs, preventDeleteImageFile); + return MediaContentproviderRepositoryImpl.deleteMedia(dbgContext, context, where, selectionArgs, preventDeleteImageFile); } @Override public ContentValues getDbContent(final long id) { - return ContentProviderMediaImpl.getDbContent(context, id); + return MediaContentproviderRepositoryImpl.getDbContent(context, id); } @Override diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/ContentProviderMediaImpl.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaContentproviderRepositoryImpl.java similarity index 98% rename from app/src/main/java/de/k3b/android/androFotoFinder/queries/ContentProviderMediaImpl.java rename to app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaContentproviderRepositoryImpl.java index a9cd82b2..65716b03 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/ContentProviderMediaImpl.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaContentproviderRepositoryImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 by k3b. + * Copyright (c) 2015-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -40,9 +40,9 @@ /** * Static Implementation of Context.getContentResolver()-ContentProvider based media api */ -public class ContentProviderMediaImpl { +public class MediaContentproviderRepositoryImpl { public static final String LOG_TAG = FotoSql.LOG_TAG + "Content"; - private static final String MODUL_NAME = ContentProviderMediaImpl.class.getName(); + private static final String MODUL_NAME = MediaContentproviderRepositoryImpl.class.getName(); public static Cursor createCursorForQuery( StringBuilder out_debugMessage, String dbgContext, final Context context, diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaImageDbReplacement.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBRepository.java similarity index 97% rename from app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaImageDbReplacement.java rename to app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBRepository.java index caa409f5..330816ba 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaImageDbReplacement.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBRepository.java @@ -61,19 +61,21 @@ import static de.k3b.android.androFotoFinder.tagDB.TagSql.SQL_COL_EXT_XMP_LAST_MODIFIED_DATE; /** + * Access Media Data through stand alone database-table. + * * 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 implements IMediaDBApi { +public class MediaDBRepository implements IMediaRepositoryApi { public static final String LOG_TAG = FotoSql.LOG_TAG + "DB"; // #155 public static final boolean debugEnabledSqlRefresh = true; - private static final String MODUL_NAME = ContentProviderMediaImpl.class.getName(); + private static final String MODUL_NAME = MediaContentproviderRepositoryImpl.class.getName(); private final SQLiteDatabase db; - public MediaImageDbReplacement(SQLiteDatabase db) { + public MediaDBRepository(SQLiteDatabase db) { this.db = db; } @@ -328,8 +330,8 @@ public long getCurrentUpdateId() { @Override public boolean mustRequery(long updateId) { final boolean modified = currentUpdateId != updateId; - if (modified && MediaImageDbReplacement.debugEnabledSqlRefresh) { - Log.i(MediaImageDbReplacement.LOG_TAG, "mustRequery: true because of " + currentUpdateReason); + if (modified && MediaDBRepository.debugEnabledSqlRefresh) { + Log.i(MediaDBRepository.LOG_TAG, "mustRequery: true because of " + currentUpdateReason); } return modified; } @@ -588,7 +590,7 @@ public static int updateMedaiCopy(Context context, SQLiteDatabase db, Date lastU if (progessListener != null) progessListener.onProgress(progress, 0, context.getString(R.string.load_db_menu_title)); - c = ContentProviderMediaImpl.createCursorForQuery(null, "updateMedaiCopy-source", context, + c = MediaContentproviderRepositoryImpl.createCursorForQuery(null, "updateMedaiCopy-source", context, query, null, null); itemCount = c.getCount(); @@ -625,7 +627,7 @@ public static int updateMedaiCopy(Context context, SQLiteDatabase db, Date lastU db.setTransactionSuccessful(); // This commits the transaction if there were no exceptions if (Global.debugEnabledSql) { java.util.Date endTime = new java.util.Date(); - final String message = "MediaImageDbReplacement.updateMedaiCopy(inserted:" + insertCout + + final String message = "MediaDBRepository.updateMedaiCopy(inserted:" + insertCout + ", updated:" + updateCount + ", toal:" + progress + " / " + itemCount + @@ -635,7 +637,7 @@ public static int updateMedaiCopy(Context context, SQLiteDatabase db, Date lastU } } catch (Exception ex) { java.util.Date endTime = new java.util.Date(); - final String message = "MediaImageDbReplacement.updateMedaiCopy(inserted:" + insertCout + + final String message = "MediaDBRepository.updateMedaiCopy(inserted:" + insertCout + ", updated:" + updateCount + ", toal:" + progress + " / " + itemCount + diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBApiWrapper.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaRepositoryApiWrapper.java similarity index 88% rename from app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBApiWrapper.java rename to app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaRepositoryApiWrapper.java index 29743daf..702de338 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBApiWrapper.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaRepositoryApiWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 by k3b. + * Copyright (c) 2019-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -28,25 +28,25 @@ import de.k3b.media.IPhotoProperties; /** - * (Default) Implementation of {@link IMediaDBApi} to forward all methods to an inner child {@link IPhotoProperties}. + * (Default) Implementation of {@link IMediaRepositoryApi} 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; - protected final IMediaDBApi transactionChild; +public class MediaRepositoryApiWrapper implements IMediaRepositoryApi { + protected final IMediaRepositoryApi readChild; + protected final IMediaRepositoryApi writeChild; + protected final IMediaRepositoryApi transactionChild; /** * count the non path write calls */ private int modifyCount = 0; - public MediaDBApiWrapper(IMediaDBApi child) { + public MediaRepositoryApiWrapper(IMediaRepositoryApi child) { this(child, child, child); } - public MediaDBApiWrapper(IMediaDBApi readChild, IMediaDBApi writeChild, IMediaDBApi transactionChild) { + public MediaRepositoryApiWrapper(IMediaRepositoryApi readChild, IMediaRepositoryApi writeChild, IMediaRepositoryApi transactionChild) { this.readChild = readChild; this.writeChild = writeChild; this.transactionChild = transactionChild; diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MergedMediaDB.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MergedMediaRepository.java similarity index 93% rename from app/src/main/java/de/k3b/android/androFotoFinder/queries/MergedMediaDB.java rename to app/src/main/java/de/k3b/android/androFotoFinder/queries/MergedMediaRepository.java index 88733f90..f8f31800 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MergedMediaDB.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MergedMediaRepository.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 by k3b. + * Copyright (c) 2019-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -30,11 +30,11 @@ * 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 and same pk. */ -public class MergedMediaDB extends MediaDBApiWrapper { - private final IMediaDBApi database; - private final IMediaDBApi contentProvider; +public class MergedMediaRepository extends MediaRepositoryApiWrapper { + private final IMediaRepositoryApi database; + private final IMediaRepositoryApi contentProvider; - public MergedMediaDB(IMediaDBApi database, IMediaDBApi contentProvider) { + public MergedMediaRepository(IMediaRepositoryApi database, IMediaRepositoryApi contentProvider) { super(database, contentProvider, database); this.database = database; this.contentProvider = contentProvider; diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagWorflow.java b/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagWorflow.java index 475b0e3d..b1629047 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagWorflow.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagWorflow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2019 by k3b. + * Copyright (c) 2017-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -30,7 +30,7 @@ import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.queries.FotoSql; -import de.k3b.android.androFotoFinder.queries.IMediaDBApi; +import de.k3b.android.androFotoFinder.queries.IMediaRepositoryApi; import de.k3b.android.util.AndroidFileCommands; import de.k3b.io.FileCommands; import de.k3b.io.IProgessListener; @@ -80,7 +80,7 @@ public TagWorflow init(Activity context, SelectedFiles selectedItems, List /** execute the updates for all affected files in the Workflow. */ public int updateTags(List addedTags, List removedTags) { - final IMediaDBApi mediaDBApi = FotoSql.getMediaDBApi(); + final IMediaRepositoryApi mediaDBApi = FotoSql.getMediaDBApi(); try { mediaDBApi.beginTransaction(); // Performance boost: all db-inserts/updates in one transaction int itemCount = 0; 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 c4d8c2fe..0b8b9a6e 100644 --- a/app/src/main/java/de/k3b/android/util/PhotoPropertiesMediaFilesScanner.java +++ b/app/src/main/java/de/k3b/android/util/PhotoPropertiesMediaFilesScanner.java @@ -45,7 +45,7 @@ import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.media.PhotoPropertiesMediaDBContentValues; import de.k3b.android.androFotoFinder.queries.FotoSql; -import de.k3b.android.androFotoFinder.queries.IMediaDBApi; +import de.k3b.android.androFotoFinder.queries.IMediaRepositoryApi; import de.k3b.android.androFotoFinder.tagDB.TagSql; import de.k3b.database.QueryParameter; import de.k3b.geo.api.GeoPointDto; @@ -160,7 +160,7 @@ public static int hideFolderMedia(Activity context, String path) { } public int updateMediaDatabase_Android42(Context context, String[] oldPathNames, String... newPathNames) { - IMediaDBApi api = FotoSql.getMediaDBApi(); + IMediaRepositoryApi api = FotoSql.getMediaDBApi(); try { api.beginTransaction(); final boolean hasNew = excludeNomediaFiles(newPathNames) > 0; diff --git a/app/src/main/java/de/k3b/android/widget/UpdateTask.java b/app/src/main/java/de/k3b/android/widget/UpdateTask.java index 05cea078..0bd81fe9 100644 --- a/app/src/main/java/de/k3b/android/widget/UpdateTask.java +++ b/app/src/main/java/de/k3b/android/widget/UpdateTask.java @@ -24,7 +24,7 @@ import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.R; import de.k3b.android.androFotoFinder.queries.FotoSql; -import de.k3b.android.androFotoFinder.queries.IMediaDBApi; +import de.k3b.android.androFotoFinder.queries.IMediaRepositoryApi; import de.k3b.android.util.AndroidFileCommands; import de.k3b.io.IProgessListener; import de.k3b.io.collections.SelectedFiles; @@ -57,7 +57,7 @@ protected Integer doInBackground(SelectedFiles... params) { result = cmd.applyExifChanges(true, exifChanges, items, null); } else { // disabled: does not work because of overlapping transactions - IMediaDBApi api = FotoSql.getMediaDBApi(); + IMediaRepositoryApi api = FotoSql.getMediaDBApi(); try { api.beginTransaction(); result = cmd.applyExifChanges(true, exifChanges, items, null); From 318f040bbec00da07c025aa6f9bd9fa9fe9fb87a Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Tue, 14 Jan 2020 19:12:23 +0100 Subject: [PATCH 35/37] #155: Refactored: Moved singleton of MediaContent2DBUpdateService from AndroFotoFinderApp to MediaContent2DBUpdateService --- .../androFotoFinder/AndroFotoFinderApp.java | 18 ++-- .../androFotoFinder/SettingsActivity.java | 2 +- .../queries/GlobalMediaContentObserver.java | 83 +++++++++++++++++++ .../queries/MediaContent2DBUpdateService.java | 1 + 4 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/de/k3b/android/androFotoFinder/queries/GlobalMediaContentObserver.java diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java index e5047e5e..1e5edca5 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java @@ -23,6 +23,7 @@ import android.app.Application; import android.content.Context; import android.database.sqlite.SQLiteDatabase; +import android.provider.MediaStore; import android.support.annotation.NonNull; import android.util.Log; import android.widget.Toast; @@ -41,6 +42,7 @@ import de.k3b.android.androFotoFinder.queries.DatabaseHelper; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.androFotoFinder.queries.FotoSqlBase; +import de.k3b.android.androFotoFinder.queries.GlobalMediaContentObserver; import de.k3b.android.androFotoFinder.queries.IMediaRepositoryApi; import de.k3b.android.androFotoFinder.queries.MediaContent2DBUpdateService; import de.k3b.android.androFotoFinder.queries.MediaContentproviderRepository; @@ -67,10 +69,9 @@ */ public class AndroFotoFinderApp extends Application { private static String fileNamePrefix = "androFotofinder.logcat-"; - private static MediaContent2DBUpdateService mediaContent2DbUpdateService = null; public static MediaContent2DBUpdateService getMediaContent2DbUpdateService() { - return mediaContent2DbUpdateService; + return MediaContent2DBUpdateService.instance; } private LogCat mCrashSaveToFile = null; @@ -107,21 +108,24 @@ public static void setMediaImageDbReplacement(Context context, boolean useMediaI final MediaDBRepository mediaDBRepository = new MediaDBRepository(writableDatabase); FotoSql.setMediaDBApi(new MergedMediaRepository(mediaDBRepository, mediaContentproviderRepository)); - AndroFotoFinderApp.mediaContent2DbUpdateService = new MediaContent2DBUpdateService(context, writableDatabase); + MediaContent2DBUpdateService.instance = new MediaContent2DBUpdateService(context, writableDatabase); if (FotoSql.getCount(new QueryParameter().addWhere("1 = 1")) == 0) { // database is empty; reload from Contentprovider - AndroFotoFinderApp.mediaContent2DbUpdateService.rebuild(context, null); + MediaContent2DBUpdateService.instance.rebuild(context, null); } + context.getApplicationContext().getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, GlobalMediaContentObserver.getInstance(context)); + context.getApplicationContext().getContentResolver().registerContentObserver(MediaStore.Files.getContentUri("external"), true, GlobalMediaContentObserver.getInstance(context)); } else { - if ((oldMediaDBApi != null) && (AndroFotoFinderApp.mediaContent2DbUpdateService != null)) { + context.getApplicationContext().getContentResolver().unregisterContentObserver(GlobalMediaContentObserver.getInstance(context)); + if ((oldMediaDBApi != null) && (MediaContent2DBUpdateService.instance != null)) { // switching from mediaImageDbReplacement to Contentprovider - AndroFotoFinderApp.mediaContent2DbUpdateService.clearMediaCopy(); + MediaContent2DBUpdateService.instance.clearMediaCopy(); } FotoSql.setMediaDBApi(mediaContentproviderRepository); - AndroFotoFinderApp.mediaContent2DbUpdateService = null; + MediaContent2DBUpdateService.instance = null; } } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java b/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java index 3ead356f..bfce1029 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java @@ -172,7 +172,7 @@ public static void prefs2Global(Context context) { LibGlobal.preferLongXmpFormat = getPref(prefs, "xmp_file_schema_long", LibGlobal.preferLongXmpFormat); Global.mapsForgeEnabled = getPref(prefs, "mapsForgeEnabled", Global.mapsForgeEnabled); - AndroFotoFinderApp.setMediaImageDbReplacement(context, getPref(prefs, "useMediaImageDbReplacement", Global.useMediaImageDbReplacement)); + AndroFotoFinderApp.setMediaImageDbReplacement(context.getApplicationContext(), getPref(prefs, "useMediaImageDbReplacement", Global.useMediaImageDbReplacement)); Global.imageDetailThumbnailIfBiggerThan = getPref(prefs, "imageDetailThumbnailIfBiggerThan" , Global.imageDetailThumbnailIfBiggerThan); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/GlobalMediaContentObserver.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/GlobalMediaContentObserver.java new file mode 100644 index 00000000..f4d2feb6 --- /dev/null +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/GlobalMediaContentObserver.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2020 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see + */ +package de.k3b.android.androFotoFinder.queries; + +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.util.Log; + +import de.k3b.android.util.DataChangeNotifyer; + +/** + * collect notifications that media content has changed + */ +public class GlobalMediaContentObserver extends ContentObserver { + private static GlobalMediaContentObserver instance = null; + private static Handler delayedChangeNotifiyHandler = null; + private static Runnable delayedRunner = null; + private static Context appContext; + private static DataChangeNotifyer.DataChangedListener dataChangedListener = null; + + private GlobalMediaContentObserver() { + super(null); + } + + public static GlobalMediaContentObserver getInstance(Context appContext) { + if (instance == null) { + GlobalMediaContentObserver.appContext = appContext; + + delayedRunner = new Runnable() { + public void run() { + onExternalDataChangeCompleted(appContext); + + } + }; + delayedChangeNotifiyHandler = new Handler(); + instance = new GlobalMediaContentObserver(); + } + return instance; + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + super.onChange(selfChange, uri); + if (!selfChange) { + Log.d(MediaDBRepository.LOG_TAG, "Media content changing " + uri); + + delayedChangeNotifiyHandler.removeCallbacks(delayedRunner); + delayedChangeNotifiyHandler.postDelayed(delayedRunner, 500); + } + } + + /** + * called in gui thread after external media-content changes are completed + * + * @param appContext + */ + private void onExternalDataChangeCompleted(Context appContext) { + Log.d(MediaDBRepository.LOG_TAG, "Media content changed "); + // todo fix database + + if (dataChangedListener != null) { + dataChangedListener.onNotifyDataChanged(); + } + } +} \ No newline at end of file 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 bcee5576..19d06314 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 @@ -33,6 +33,7 @@ * {@link MediaContentproviderRepository} are transfered to {@link MediaDBRepository} */ public class MediaContent2DBUpdateService { + public static MediaContent2DBUpdateService instance = null; private final Context context; private final SQLiteDatabase writableDatabase; From 5f9b8a9f00d7c7da32894fe7483ed95f46c60922 Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Tue, 28 Jan 2020 20:18:29 +0100 Subject: [PATCH 36/37] #155: Refactored: Moved notivication logic to PhotoChangeNotifyer and GlobalMediaContentObserver --- .../androFotoFinder/AndroFotoFinderApp.java | 7 +- .../directory/DirectoryListAdapter.java | 13 +- .../directory/DirectoryPickerFragment.java | 9 +- .../directory/SaveAsPickerFragment.java | 9 +- .../gallery/cursor/GalleryCursorAdapter.java | 13 +- .../gallery/cursor/GalleryCursorFragment.java | 242 +++++++++--------- .../ImageDetailActivityViewPager.java | 136 +++++----- .../ImagePagerAdapterFromCursor.java | 13 +- .../queries/GlobalMediaContentObserver.java | 12 +- .../tagDB/TagsPickerFragment.java | 12 +- .../k3b/android/util/AndroidFileCommands.java | 10 +- .../k3b/android/util/DataChangeNotifyer.java | 33 --- .../k3b/android/util/PhotoChangeNotifyer.java | 61 +++++ ...oPropertiesMediaFilesScannerAsyncTask.java | 4 +- .../k3b/android/widget/BaseQueryActivity.java | 7 +- 15 files changed, 323 insertions(+), 258 deletions(-) delete mode 100644 app/src/main/java/de/k3b/android/util/DataChangeNotifyer.java create mode 100644 app/src/main/java/de/k3b/android/util/PhotoChangeNotifyer.java diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java index 1e5edca5..f516d93c 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java @@ -23,7 +23,6 @@ import android.app.Application; import android.content.Context; import android.database.sqlite.SQLiteDatabase; -import android.provider.MediaStore; import android.support.annotation.NonNull; import android.util.Log; import android.widget.Toast; @@ -51,6 +50,7 @@ import de.k3b.android.androFotoFinder.queries.MergedMediaRepository; import de.k3b.android.osmdroid.forge.MapsForgeSupport; import de.k3b.android.util.LogCat; +import de.k3b.android.util.PhotoChangeNotifyer; import de.k3b.android.widget.ActivityWithCallContext; import de.k3b.android.widget.LocalizedActivity; import de.k3b.database.QueryParameter; @@ -115,11 +115,10 @@ public static void setMediaImageDbReplacement(Context context, boolean useMediaI MediaContent2DBUpdateService.instance.rebuild(context, null); } - context.getApplicationContext().getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, GlobalMediaContentObserver.getInstance(context)); - context.getApplicationContext().getContentResolver().registerContentObserver(MediaStore.Files.getContentUri("external"), true, GlobalMediaContentObserver.getInstance(context)); + PhotoChangeNotifyer.registerContentObserver(context, GlobalMediaContentObserver.getInstance(context)); } else { - context.getApplicationContext().getContentResolver().unregisterContentObserver(GlobalMediaContentObserver.getInstance(context)); + PhotoChangeNotifyer.unregisterContentObserver(context, GlobalMediaContentObserver.getInstance(context)); if ((oldMediaDBApi != null) && (MediaContent2DBUpdateService.instance != null)) { // switching from mediaImageDbReplacement to Contentprovider MediaContent2DBUpdateService.instance.clearMediaCopy(); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryListAdapter.java b/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryListAdapter.java index 2448ac65..9865b073 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryListAdapter.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryListAdapter.java @@ -32,9 +32,9 @@ import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.R; +import de.k3b.android.util.PhotoChangeNotifyer; import de.k3b.io.AlbumFile; import de.k3b.io.Directory; -import de.k3b.io.FileUtils; import de.k3b.io.IDirectory; import de.k3b.io.IExpandableListViewNavigation; @@ -42,7 +42,8 @@ * Maps android independent IExpandableListViewNavigation to android specific ExpandableListAdapter so it can be viewed in ExpandableList */ -public class DirectoryListAdapter extends BaseExpandableListAdapter implements IExpandableListViewNavigation { +public class DirectoryListAdapter extends BaseExpandableListAdapter implements + IExpandableListViewNavigation, PhotoChangeNotifyer.PhotoChangedListener { private LayoutInflater inflater; @@ -219,4 +220,12 @@ public static Spanned getDirectoryDisplayText(String prefix, IDirectory director Directory.appendCount(result, directory, options); return Html.fromHtml(result.toString()); } + + /** + * PhotoChangeNotifyer.PhotoChangedListener + **/ + @Override + public void onNotifyPhotoChanged() { + notifyDataSetChanged(); + } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryPickerFragment.java b/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryPickerFragment.java index 84f33d02..92530d12 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryPickerFragment.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryPickerFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 by k3b. + * Copyright (c) 2015-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -70,6 +70,7 @@ import de.k3b.android.util.ClipboardUtil; import de.k3b.android.util.FileManagerUtil; import de.k3b.android.util.IntentUtil; +import de.k3b.android.util.PhotoChangeNotifyer; import de.k3b.android.util.PhotoPropertiesMediaFilesScanner; import de.k3b.android.widget.Dialogs; import de.k3b.database.QueryParameter; @@ -456,8 +457,8 @@ protected boolean onPopUpClick(MenuItem menuItem, IDirectory popUpSelection) { } } - public void notifyDataSetChanged() { - if (this.mAdapter != null) this.mAdapter.notifyDataSetChanged(); + public void notifyPhotoChanged() { + PhotoChangeNotifyer.notifyPhotoChanged(this.getActivity(), this.mAdapter); } private boolean onCopy(IDirectory selection) { @@ -607,7 +608,7 @@ private void onRenameDirAnswer(final IDirectory srcDir, String newFolderName) { } else { // update dirpicker srcDir.rename(srcDirFile.getName(), newFolderName); - notifyDataSetChanged(); + notifyPhotoChanged(); } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/directory/SaveAsPickerFragment.java b/app/src/main/java/de/k3b/android/androFotoFinder/directory/SaveAsPickerFragment.java index ffcd0231..1e72545c 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/directory/SaveAsPickerFragment.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/directory/SaveAsPickerFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 by k3b. + * Copyright (c) 2018-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -20,7 +20,6 @@ package de.k3b.android.androFotoFinder.directory; import android.app.Activity; -import android.net.Uri; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -29,11 +28,8 @@ import java.io.File; -import de.k3b.android.androFotoFinder.AffUtils; import de.k3b.android.androFotoFinder.R; -import de.k3b.android.androFotoFinder.imagedetail.ImageDetailActivityViewPager; import de.k3b.android.androFotoFinder.queries.FotoSql; -import de.k3b.android.util.AndroidFileCommands; import de.k3b.android.util.OsUtils; import de.k3b.io.AlbumFile; import de.k3b.io.FileUtils; @@ -41,7 +37,6 @@ import de.k3b.io.OSDirOrVirtualAlbumFile; import de.k3b.io.OSDirectory; import de.k3b.io.StringUtils; -import de.k3b.io.collections.SelectedFiles; /** * a picker with a fale name field and a directory picker. @@ -132,7 +127,7 @@ protected void onDirectoryPick(IDirectory selection) { // close dialog and return to caller super.onDirectoryPick(result); onFilePick(new File(result.getAbsolute())); - this.notifyDataSetChanged(); + this.notifyPhotoChanged(); dismiss(); } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/gallery/cursor/GalleryCursorAdapter.java b/app/src/main/java/de/k3b/android/androFotoFinder/gallery/cursor/GalleryCursorAdapter.java index 972cf9ef..c6aa79fe 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/gallery/cursor/GalleryCursorAdapter.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/gallery/cursor/GalleryCursorAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 by k3b. + * Copyright (c) 2015-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -40,6 +40,7 @@ import de.k3b.android.androFotoFinder.imagedetail.HugeImageLoader; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.util.DBUtils; +import de.k3b.android.util.PhotoChangeNotifyer; import de.k3b.io.collections.SelectedFiles; import de.k3b.io.collections.SelectedItems; import de.k3b.media.PhotoPropertiesUtil; @@ -60,7 +61,7 @@ * * Created by k3b on 02.06.2015. */ -public class GalleryCursorAdapter extends CursorAdapter { +public class GalleryCursorAdapter extends CursorAdapter implements PhotoChangeNotifyer.PhotoChangedListener { private static final int MAX_IMAGE_DIMENSION = HugeImageLoader.getMaxTextureSize(); // Identifies a particular Loader or a LoaderManager being used in this component @@ -234,4 +235,12 @@ public long getImageId(int position) { return DBUtils.getLong(cursor, FotoSql.SQL_COL_PK, 0); } + /** + * PhotoChangeNotifyer.PhotoChangedListener + **/ + @Override + public void onNotifyPhotoChanged() { + notifyDataSetChanged(); + } + } 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 e3d5d693..a06d6094 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 @@ -82,8 +82,8 @@ import de.k3b.android.androFotoFinder.tagDB.TagsPickerFragment; import de.k3b.android.util.AndroidFileCommands; import de.k3b.android.util.DBUtils; -import de.k3b.android.util.DataChangeNotifyer; import de.k3b.android.util.OsUtils; +import de.k3b.android.util.PhotoChangeNotifyer; import de.k3b.android.util.PhotoPropertiesMediaFilesScanner; import de.k3b.android.util.ResourceUtils; import de.k3b.android.widget.AboutDialogPreference; @@ -121,7 +121,7 @@ * if (view-non-select) menu_gallery_non_selected_only + menu_gallery_non_multiselect */ public class GalleryCursorFragment extends Fragment implements Queryable, DirectoryGui, Common, - TagsPickerFragment.ITagsPicker, DataChangeNotifyer.DataChangedListener { + TagsPickerFragment.ITagsPicker, PhotoChangeNotifyer.PhotoChangedListener { private static final String INSTANCE_STATE_LAST_VISIBLE_POSITION = "lastVisiblePosition"; private static final String INSTANCE_STATE_SELECTED_ITEM_IDS = "selectedItems"; private static final String INSTANCE_STATE_OLD_TITLE = "oldTitle"; @@ -209,7 +209,7 @@ public SelectedItems getSelectedItems() { return mSelectedItems; } - public void onNotifyDataChanged() { + public void onNotifyPhotoChanged() { requeryIfDataHasChanged(); } @@ -243,7 +243,7 @@ private void requery(String why) { private boolean cmdMoveOrCopyWithDestDirPicker(final boolean move, String lastCopyToPath, final SelectedFiles fotos) { if (AndroidFileCommands.canProcessFile(this.getActivity(), false)) { - DataChangeNotifyer.setDataChangedListener(this); + PhotoChangeNotifyer.setPhotoChangedListener(this); mDestDirPicker = MoveOrCopyDestDirPicker.newInstance(move, fotos); mDestDirPicker.defineDirectoryNavigation(OsUtils.getRootOSDirectory(null), @@ -551,7 +551,7 @@ protected void doInBackground(Long id, Cursor cursor) { protected void onPostExecute(SelectedItems selectedItems) { if (!isCancelled()) { onMissingDisplayNamesComplete(mStatus); - onNotifyDataChanged(); + onNotifyPhotoChanged(); } } }; @@ -935,7 +935,7 @@ protected void onPostExecute(SelectedItems selectedItems) { } else { onDuplicatesFound(null, mStatus); } - onNotifyDataChanged(); + onNotifyPhotoChanged(); } else { if (mStatus != null) { mStatus.append("\nTask canceled"); @@ -1039,109 +1039,8 @@ protected void onDirectoryPick(IDirectory selection) { } } - class LocalCursorLoader implements LoaderManager.LoaderCallbacks { - /** - * called by LoaderManager.getLoader(ACTIVITY_ID) to (re)create loader - * that attaches to last query/cursor if it still exist i.e. after rotation - */ - @Override - public Loader onCreateLoader(int aLoaderID, Bundle bundle) { - if (loaderID == aLoaderID) { - QueryParameter query = getCurrentQuery(); - mRequeryInstanceCount++; - if (Global.debugEnabledSql) { - Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onCreateLoader" - + getDebugContext() + - " : query = " + query); - } - return FotoSql.createCursorLoader(getActivity().getApplicationContext(), query); - } - - // An invalid id was passed in - return null; - } - - /** - * called after media db content has changed - */ - @Override - public void onLoadFinished(Loader _loader, Cursor data) { - mLastVisiblePosition = mGalleryView.getLastVisiblePosition(); - - final Activity context = getActivity(); - if (data == null) { - 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) { - if (0 != loader.getQuery().toSqlString().compareTo(getCurrentQuery(FotoSql.queryDetail).toSqlString())) { - // query is not default query. revert to default query - mGalleryContentQuery = FotoSql.queryDetail; - requery("requery after query-errror"); - title = context.getString(R.string.global_err_sql_title_reload); - } else { - title = context.getString(R.string.global_err_system); - context.finish(); - } - Dialogs.messagebox(context, title, message); - return; - } - } - - mUpdateId = FotoSql.getMediaDBApi().getCurrentUpdateId(); - // do change the data - mAdapter.swapCursor(data); - - if (mLastVisiblePosition > 0) { - mGalleryView.smoothScrollToPosition(mLastVisiblePosition); - mLastVisiblePosition = -1; - } - - final int resultCount = (data == null) ? 0 : data.getCount(); - if (Global.debugEnabled) { - Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onLoadFinished" - + getDebugContext() + - " fount " + resultCount + " rows"); - } - - // do change the data - mAdapter.notifyDataSetChanged(); - - if (mLastVisiblePosition > 0) { - mGalleryView.smoothScrollToPosition(mLastVisiblePosition); - mLastVisiblePosition = -1; - } - - // show the changes - - if (context instanceof OnGalleryInteractionListener) { - ((OnGalleryInteractionListener) context).setResultCount(resultCount); - } - multiSelectionReplaceTitleIfNecessary(); - } - - /** - * called by LoaderManager. after search criteria were changed or if activity is destroyed. - */ - @Override - public void onLoaderReset(Loader loader) { - if (Global.debugEnabled) { - Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onLoaderReset" + getDebugContext()); - } - // rember position where we have to scroll to after refreshLocal is finished. - mLastVisiblePosition = mGalleryView.getLastVisiblePosition(); - - mAdapter.swapCursor(null); - mAdapter.notifyDataSetChanged(); - } - - @NonNull - protected String getDebugContext() { - return "(@" + loaderID + ", #" + mRequeryInstanceCount + - ", LastVisiblePosition=" + mLastVisiblePosition + -// ", Path='" + mInitialFilePath + - "')"; - } + public void notifyPhotoChanged() { + PhotoChangeNotifyer.notifyPhotoChanged(this.getActivity(), this.mAdapter); } private boolean onPickOk() { @@ -1415,25 +1314,109 @@ private void onMissingDisplayNamesComplete(StringBuffer debugMessage) { } } - private class TagUpdateTask extends TagTask> { - - TagUpdateTask(SelectedFiles fotos) { - super(GalleryCursorFragment.this.getActivity(), R.string.tags_activity_title); - this.getWorkflow().init(GalleryCursorFragment.this.getActivity(), fotos, null); + class LocalCursorLoader implements LoaderManager.LoaderCallbacks { + /** + * called by LoaderManager.getLoader(ACTIVITY_ID) to (re)create loader + * that attaches to last query/cursor if it still exist i.e. after rotation + */ + @Override + public Loader onCreateLoader(int aLoaderID, Bundle bundle) { + if (loaderID == aLoaderID) { + QueryParameter query = getCurrentQuery(); + mRequeryInstanceCount++; + if (Global.debugEnabledSql) { + Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onCreateLoader" + + getDebugContext() + + " : query = " + query); + } + return FotoSql.createCursorLoader(getActivity().getApplicationContext(), query); + } + // An invalid id was passed in + return null; } + /** + * called after media db content has changed + */ @Override - protected Integer doInBackground(List... params) { - return getWorkflow().updateTags(params[0], params[1]); + public void onLoadFinished(Loader _loader, Cursor data) { + mLastVisiblePosition = mGalleryView.getLastVisiblePosition(); + + final Activity context = getActivity(); + if (data == null) { + 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) { + if (0 != loader.getQuery().toSqlString().compareTo(getCurrentQuery(FotoSql.queryDetail).toSqlString())) { + // query is not default query. revert to default query + mGalleryContentQuery = FotoSql.queryDetail; + requery("requery after query-errror"); + title = context.getString(R.string.global_err_sql_title_reload); + } else { + title = context.getString(R.string.global_err_system); + context.finish(); + } + Dialogs.messagebox(context, title, message); + return; + } + } + + mUpdateId = FotoSql.getMediaDBApi().getCurrentUpdateId(); + // do change the data + mAdapter.swapCursor(data); + + if (mLastVisiblePosition > 0) { + mGalleryView.smoothScrollToPosition(mLastVisiblePosition); + mLastVisiblePosition = -1; + } + + final int resultCount = (data == null) ? 0 : data.getCount(); + if (Global.debugEnabled) { + Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onLoadFinished" + + getDebugContext() + + " fount " + resultCount + " rows"); + } + + // do change the data + notifyPhotoChanged(); + + if (mLastVisiblePosition > 0) { + mGalleryView.smoothScrollToPosition(mLastVisiblePosition); + mLastVisiblePosition = -1; + } + + // show the changes + + if (context instanceof OnGalleryInteractionListener) { + ((OnGalleryInteractionListener) context).setResultCount(resultCount); + } + multiSelectionReplaceTitleIfNecessary(); } + /** + * called by LoaderManager. after search criteria were changed or if activity is destroyed. + */ @Override - protected void onPostExecute(Integer itemCount) { - super.onPostExecute(itemCount); - onNotifyDataChanged(); + public void onLoaderReset(Loader loader) { + if (Global.debugEnabled) { + Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onLoaderReset" + getDebugContext()); + } + // rember position where we have to scroll to after refreshLocal is finished. + mLastVisiblePosition = mGalleryView.getLastVisiblePosition(); + + mAdapter.swapCursor(null); + notifyPhotoChanged(); } + @NonNull + protected String getDebugContext() { + return "(@" + loaderID + ", #" + mRequeryInstanceCount + + ", LastVisiblePosition=" + mLastVisiblePosition + +// ", Path='" + mInitialFilePath + + "')"; + } } /** is called when removeDuplicates() found duplicates */ @@ -1512,4 +1495,25 @@ private boolean toggleSelection(long imageID) { } } + private class TagUpdateTask extends TagTask> { + + TagUpdateTask(SelectedFiles fotos) { + super(GalleryCursorFragment.this.getActivity(), R.string.tags_activity_title); + this.getWorkflow().init(GalleryCursorFragment.this.getActivity(), fotos, null); + + } + + @Override + protected Integer doInBackground(List... params) { + return getWorkflow().updateTags(params[0], params[1]); + } + + @Override + protected void onPostExecute(Integer itemCount) { + super.onPostExecute(itemCount); + onNotifyPhotoChanged(); + } + + } + } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java index 52553ef0..89edc228 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java @@ -64,10 +64,10 @@ import de.k3b.android.androFotoFinder.tagDB.TagTask; import de.k3b.android.androFotoFinder.tagDB.TagsPickerFragment; import de.k3b.android.util.AndroidFileCommands; -import de.k3b.android.util.DataChangeNotifyer; import de.k3b.android.util.FileManagerUtil; import de.k3b.android.util.IntentUtil; import de.k3b.android.util.OsUtils; +import de.k3b.android.util.PhotoChangeNotifyer; import de.k3b.android.util.PhotoPropertiesMediaFilesScanner; import de.k3b.android.util.PhotoPropertiesMediaFilesScannerAsyncTask; import de.k3b.android.widget.AboutDialogPreference; @@ -94,7 +94,7 @@ */ public class ImageDetailActivityViewPager extends ActivityWithAutoCloseDialogs implements Common, TagsPickerFragment.ITagsPicker, - DataChangeNotifyer.DataChangedListener { + PhotoChangeNotifyer.PhotoChangedListener { private static final String INSTANCE_STATE_MODIFY_COUNT = "mModifyCount"; private static final String INSTANCE_STATE_LAST_SCROLL_POSITION = "lastScrollPosition"; /** #70: remember on config change (screen rotation) */ @@ -250,7 +250,7 @@ public boolean onOptionsItemSelected(MenuItem menuItem) { result = cmdMoveOrCopyWithDestDirPicker(true, mFileCommands.getLastCopyToPath(), getCurrentFoto()); break; case R.id.menu_item_rename: - DataChangeNotifyer.setDataChangedListener(this); + PhotoChangeNotifyer.setPhotoChangedListener(this); result = onRenameQueston(getCurrentFoto(), getCurrentImageId(), getCurrentFilePath(), null); break; case R.id.menu_exif: @@ -566,7 +566,7 @@ protected void onPause() { unhideActionBar(DISABLE_HIDE_ACTIONBAR, "onPause"); Global.debugMemory(mDebugPrefix, "onPause"); startStopSlideShow(false); - DataChangeNotifyer.setDataChangedListener(null); // notify triggererd in onResume + PhotoChangeNotifyer.setPhotoChangedListener(null); // notify triggererd in onResume super.onPause(); } @@ -682,7 +682,7 @@ private void onRenameAnswer(SelectedFiles currentFoto, final long fotoId, final errorMessage = getString(R.string.image_err_file_exists_format, dest.getAbsoluteFile()); } - DataChangeNotifyer.setDataChangedListener(this); + PhotoChangeNotifyer.setPhotoChangedListener(this); if (errorMessage != null) { // dest-file already exists Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show(); @@ -914,7 +914,7 @@ public boolean onPrepareOptionsMenu(Menu menu) { private boolean cmdMoveOrCopyWithDestDirPicker(final boolean move, String lastCopyToPath, final SelectedFiles fotos) { if (AndroidFileCommands.canProcessFile(this, false)) { - DataChangeNotifyer.setDataChangedListener(this); + PhotoChangeNotifyer.setPhotoChangedListener(this); mDestDirPicker = MoveOrCopyDestDirPicker.newInstance(move, fotos); mDestDirPicker.defineDirectoryNavigation(OsUtils.getRootOSDirectory(null), @@ -1009,7 +1009,7 @@ private boolean onEditExif(MenuItem menuItem, SelectedFiles currentFoto, final l return true; } - public void onNotifyDataChanged() { + public void onNotifyPhotoChanged() { requeryIfDataHasChanged(); } @@ -1041,7 +1041,7 @@ private boolean tagsShowEditDialog(SelectedFiles fotos) { dlg.setRemoveNames(new ArrayList()); dlg.setBaseQuery(mGalleryContentQuery); setAutoClose(dlg, null, null); - DataChangeNotifyer.setDataChangedListener(this); + PhotoChangeNotifyer.setPhotoChangedListener(this); dlg.show(getFragmentManager(), "editTags"); return true; } @@ -1184,6 +1184,41 @@ private void setContextMode(Object modeName) { } } + public void notifyPhotoChanged() { + PhotoChangeNotifyer.notifyPhotoChanged(this, this.mAdapter); + } + + class LocalFileCommands extends AndroidFileCommands { + @Override + protected void onPostProcess(String what, int opCode, SelectedFiles selectedFiles, int modifyCount, int itemCount, String[] oldPathNames, String[] newPathNames) { + mInitialFilePath = null; + switch (opCode) { + case OP_MOVE: + case OP_RENAME: + if ((newPathNames != null) && (newPathNames.length > 0)) { + // so selection will be restored to this after load complete + mInitialFilePath = newPathNames[0]; + } + break; + case OP_COPY: + if ((oldPathNames != null) && (oldPathNames.length > 0)) { + // so selection will be restored to this after load complete + mInitialFilePath = oldPathNames[0]; + } + break; + default: + break; + } + + super.onPostProcess(what, opCode, selectedFiles, modifyCount, itemCount, oldPathNames, newPathNames); + + if ((opCode == OP_RENAME) || (opCode == OP_MOVE) || (opCode == OP_DELETE) || (opCode == OP_RENAME)) { + reloadIfDataHasChanged(); + } + } + + } + public static class MoveOrCopyDestDirPicker extends DirectoryPickerFragment { protected static AndroidFileCommands sFileCommands = null; @@ -1233,41 +1268,42 @@ protected void onDirectoryPick(IDirectory selection) { mModifyCount++; // copy or move initiated getActivity().setResult((mModifyCount == 0) ? RESULT_NOCHANGE : RESULT_CHANGE); - DataChangeNotifyer.setDataChangedListener(((ImageDetailActivityViewPager) getActivity())); + PhotoChangeNotifyer.setPhotoChangedListener(((ImageDetailActivityViewPager) getActivity())); sFileCommands.onMoveOrCopyDirectoryPick(getMove(), getSrcFotos(), selection); dismiss(); } } - class LocalFileCommands extends AndroidFileCommands { + private class TagUpdateTask extends TagTask> { + + TagUpdateTask(SelectedFiles fotos) { + super(ImageDetailActivityViewPager.this, R.string.tags_activity_title); + this.getWorkflow().init(ImageDetailActivityViewPager.this, fotos, null); + + } + @Override - protected void onPostProcess(String what, int opCode, SelectedFiles selectedFiles, int modifyCount, int itemCount, String[] oldPathNames, String[] newPathNames) { - mInitialFilePath = null; - switch (opCode) { - case OP_MOVE: - case OP_RENAME: - if ((newPathNames != null) && (newPathNames.length > 0)) { - // so selection will be restored to this after load complete - mInitialFilePath = newPathNames[0]; - } - break; - case OP_COPY: - if ((oldPathNames != null) && (oldPathNames.length > 0)) { - // so selection will be restored to this after load complete - mInitialFilePath = oldPathNames[0]; - } - break; - default: - break; - } + protected Integer doInBackground(List... params) { + return getWorkflow().updateTags(params[0], params[1]); + } - super.onPostProcess(what, opCode, selectedFiles, modifyCount, itemCount, oldPathNames, newPathNames); + @Override + protected void onPostExecute(Integer itemCount) { + super.onPostExecute(itemCount); + reloadIfDataHasChanged(); + } + } - if ((opCode == OP_RENAME) || (opCode == OP_MOVE) || (opCode == OP_DELETE) || (opCode == OP_RENAME)) { - reloadIfDataHasChanged(); + /** + * #70: adds/removes/replases contextColumnExpression + */ + private static void addContextColumn(QueryParameter query, String contextColumnExpression) { + if (query != null) { + query.removeFirstColumnThatContains(CONTEXT_COLUMN_ALIAS); + if ((contextColumnExpression != null) && (contextColumnExpression.trim().length() > 0)) { + query.addColumn(contextColumnExpression + CONTEXT_COLUMN_ALIAS); } } - } /** @@ -1323,7 +1359,7 @@ public void onLoadFinished(Loader loader, Cursor data) { } // do change the data - mAdapter.notifyDataSetChanged(); + notifyPhotoChanged(); mViewPager.setAdapter(mAdapter); // show the changes @@ -1342,7 +1378,7 @@ public void onLoaderReset(Loader loader) { Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onLoaderReset" + getDebugContext()); } - mAdapter.notifyDataSetChanged(); + notifyPhotoChanged(); } @NonNull @@ -1353,34 +1389,4 @@ private String getDebugContext() { "')"; } } - - private class TagUpdateTask extends TagTask> { - - TagUpdateTask(SelectedFiles fotos) { - super(ImageDetailActivityViewPager.this, R.string.tags_activity_title); - this.getWorkflow().init(ImageDetailActivityViewPager.this, fotos, null); - - } - - @Override - protected Integer doInBackground(List... params) { - return getWorkflow().updateTags(params[0], params[1]); - } - - @Override - protected void onPostExecute(Integer itemCount) { - super.onPostExecute(itemCount); - reloadIfDataHasChanged(); - } - } - - /** #70: adds/removes/replases contextColumnExpression */ - private static void addContextColumn(QueryParameter query, String contextColumnExpression) { - if (query != null) { - query.removeFirstColumnThatContains(CONTEXT_COLUMN_ALIAS); - if ((contextColumnExpression != null) && (contextColumnExpression.trim().length() > 0)) { - query.addColumn(contextColumnExpression + CONTEXT_COLUMN_ALIAS); - } - } - } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImagePagerAdapterFromCursor.java b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImagePagerAdapterFromCursor.java index a26e5276..4b42854c 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImagePagerAdapterFromCursor.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImagePagerAdapterFromCursor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 by k3b. + * Copyright (c) 2015-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -49,6 +49,7 @@ import de.k3b.android.util.DBUtils; import de.k3b.android.util.GarbageCollector; import de.k3b.android.util.MenuUtils; +import de.k3b.android.util.PhotoChangeNotifyer; import de.k3b.android.util.ResourceUtils; import de.k3b.media.PhotoPropertiesBulkUpdateService; @@ -58,7 +59,7 @@ * Translates between position in ViewPager and content page content with image * Created by k3b on 04.07.2015. */ -public class ImagePagerAdapterFromCursor extends PagerAdapter { +public class ImagePagerAdapterFromCursor extends PagerAdapter implements PhotoChangeNotifyer.PhotoChangedListener { private static final int MAX_IMAGE_DIMENSION = HugeImageLoader.getMaxTextureSize(); /** colum alias for optinal sql expression to show ContextDetails */ @@ -463,4 +464,12 @@ public int getMenuId() { public void setContext(MenuItem context) { if (mImageButtonController != null) mImageButtonController.setContext(context); } + + /** + * PhotoChangeNotifyer.PhotoChangedListener + **/ + @Override + public void onNotifyPhotoChanged() { + notifyDataSetChanged(); + } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/GlobalMediaContentObserver.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/GlobalMediaContentObserver.java index f4d2feb6..8bfb9fd0 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/GlobalMediaContentObserver.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/GlobalMediaContentObserver.java @@ -24,7 +24,7 @@ import android.os.Handler; import android.util.Log; -import de.k3b.android.util.DataChangeNotifyer; +import de.k3b.android.util.PhotoChangeNotifyer; /** * collect notifications that media content has changed @@ -34,13 +34,13 @@ public class GlobalMediaContentObserver extends ContentObserver { private static Handler delayedChangeNotifiyHandler = null; private static Runnable delayedRunner = null; private static Context appContext; - private static DataChangeNotifyer.DataChangedListener dataChangedListener = null; + private static PhotoChangeNotifyer.PhotoChangedListener photoChangedListener = null; private GlobalMediaContentObserver() { super(null); } - public static GlobalMediaContentObserver getInstance(Context appContext) { + public static GlobalMediaContentObserver getInstance(final Context appContext) { if (instance == null) { GlobalMediaContentObserver.appContext = appContext; @@ -72,12 +72,12 @@ public void onChange(boolean selfChange, Uri uri) { * * @param appContext */ - private void onExternalDataChangeCompleted(Context appContext) { + private static void onExternalDataChangeCompleted(Context appContext) { Log.d(MediaDBRepository.LOG_TAG, "Media content changed "); // todo fix database - if (dataChangedListener != null) { - dataChangedListener.onNotifyDataChanged(); + if (photoChangedListener != null) { + photoChangedListener.onNotifyPhotoChanged(); } } } \ No newline at end of file diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagsPickerFragment.java b/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagsPickerFragment.java index 262f7c33..4d34dbec 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagsPickerFragment.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagsPickerFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2019 by k3b. + * Copyright (c) 2017-2020 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -843,10 +843,10 @@ private int tagAdd(Tag parent, String itemExpression) { } } mDataAdapter.notifyDataSetInvalidated(); - mDataAdapter.notifyDataSetChanged(); + notifyDataSetChanged(); mDataAdapter.reloadList(); TagRepository.getInstance().save(); - mDataAdapter.notifyDataSetChanged(); + notifyDataSetChanged(); } return changeCount; } @@ -860,7 +860,7 @@ private void tagChange(Tag tag, Tag parent) { mDataAdapter.reloadList(); } TagRepository.getInstance().save(); - mDataAdapter.notifyDataSetChanged(); + notifyDataSetChanged(); } private boolean onPaste(Tag currentMenuSelection) { @@ -940,4 +940,8 @@ private void refershResultList() { private void refreshCounter() { TagsPickerFragment.this.mDataAdapter.getCount(); } + + public void notifyDataSetChanged() { + mDataAdapter.notifyDataSetChanged(); + } } diff --git a/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java b/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java index 8d001f99..127ab497 100644 --- a/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java +++ b/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java @@ -173,12 +173,12 @@ private static int getResourceId(int opCode) { } - public boolean onOptionsItemSelected(final MenuItem item, final SelectedFiles selectedFileNames, DataChangeNotifyer.DataChangedListener dataChangedListener) { + public boolean onOptionsItemSelected(final MenuItem item, final SelectedFiles selectedFileNames, PhotoChangeNotifyer.PhotoChangedListener photoChangedListener) { if ((selectedFileNames != null) && (selectedFileNames.size() > 0)) { // Handle item selection switch (item.getItemId()) { case R.id.cmd_delete: - return cmdDeleteFileWithQuestion(selectedFileNames, dataChangedListener); + return cmdDeleteFileWithQuestion(selectedFileNames, photoChangedListener); default:break; } } @@ -277,7 +277,7 @@ private void setLastCopyToPath(String copyToPath) { } public boolean cmdDeleteFileWithQuestion(final SelectedFiles fotos, - final DataChangeNotifyer.DataChangedListener dataChangedListener) { + final PhotoChangeNotifyer.PhotoChangedListener photoChangedListener) { String[] pathNames = fotos.getFileNames(); String errorMessage = checkWriteProtected(R.string.delete_menu_title, SelectedFiles.getFiles(pathNames)); @@ -308,8 +308,8 @@ public void onClick( final int id) { mActiveAlert = null; deleteFiles(fotos, null); - if (dataChangedListener != null) { - dataChangedListener.onNotifyDataChanged(); + if (photoChangedListener != null) { + photoChangedListener.onNotifyPhotoChanged(); } } } diff --git a/app/src/main/java/de/k3b/android/util/DataChangeNotifyer.java b/app/src/main/java/de/k3b/android/util/DataChangeNotifyer.java deleted file mode 100644 index 54371bcf..00000000 --- a/app/src/main/java/de/k3b/android/util/DataChangeNotifyer.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2019-2020 by k3b. - * - * This file is part of AndroFotoFinder / #APhotoManager. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see - */ - -package de.k3b.android.util; - -public class DataChangeNotifyer { - public static DataChangedListener dataChangedListener = null; - - public static void setDataChangedListener(DataChangedListener dataChangedListener) { - DataChangeNotifyer.dataChangedListener = dataChangedListener; - } - - public interface DataChangedListener { - void onNotifyDataChanged(); - } - -} diff --git a/app/src/main/java/de/k3b/android/util/PhotoChangeNotifyer.java b/app/src/main/java/de/k3b/android/util/PhotoChangeNotifyer.java new file mode 100644 index 00000000..da422188 --- /dev/null +++ b/app/src/main/java/de/k3b/android/util/PhotoChangeNotifyer.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019-2020 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see + */ + +package de.k3b.android.util; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.provider.MediaStore; + +import de.k3b.android.androFotoFinder.queries.FotoSql; + +/** + * implements hiding Android specific Data change notifikation + **/ +public class PhotoChangeNotifyer { + public static PhotoChangedListener photoChangedListener = null; + + public static void setPhotoChangedListener(PhotoChangedListener photoChangedListener) { + PhotoChangeNotifyer.photoChangedListener = photoChangedListener; + } + + public static void notifyPhotoChanged(Context context, PhotoChangedListener adapter) { + if (adapter != null) adapter.onNotifyPhotoChanged(); + + if (false) { + context.getApplicationContext().getContentResolver().notifyChange(FotoSql.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE, null, false); + } + } + + public static final void registerContentObserver(Context context, ContentObserver observer) { + final ContentResolver contentResolver = context.getApplicationContext().getContentResolver(); + contentResolver.registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, observer); + contentResolver.registerContentObserver(MediaStore.Files.getContentUri("external"), true, observer); + } + + public static void unregisterContentObserver(Context context, ContentObserver instance) { + context.getApplicationContext().getContentResolver().unregisterContentObserver(instance); + } + + public interface PhotoChangedListener { + void onNotifyPhotoChanged(); + } + +} \ No newline at end of file diff --git a/app/src/main/java/de/k3b/android/util/PhotoPropertiesMediaFilesScannerAsyncTask.java b/app/src/main/java/de/k3b/android/util/PhotoPropertiesMediaFilesScannerAsyncTask.java index f05c7f91..0fab7106 100644 --- a/app/src/main/java/de/k3b/android/util/PhotoPropertiesMediaFilesScannerAsyncTask.java +++ b/app/src/main/java/de/k3b/android/util/PhotoPropertiesMediaFilesScannerAsyncTask.java @@ -53,8 +53,8 @@ protected Integer doInBackground(String[]... pathNames) { private static void notifyIfThereAreChanges(Integer modifyCount, Context context, String why) { if (modifyCount > 0) { PhotoPropertiesMediaFilesScanner.notifyChanges(context, why); - if (DataChangeNotifyer.dataChangedListener != null) { - DataChangeNotifyer.dataChangedListener.onNotifyDataChanged(); + if (PhotoChangeNotifyer.photoChangedListener != null) { + PhotoChangeNotifyer.photoChangedListener.onNotifyPhotoChanged(); } } diff --git a/app/src/main/java/de/k3b/android/widget/BaseQueryActivity.java b/app/src/main/java/de/k3b/android/widget/BaseQueryActivity.java index eae81b05..99534cde 100644 --- a/app/src/main/java/de/k3b/android/widget/BaseQueryActivity.java +++ b/app/src/main/java/de/k3b/android/widget/BaseQueryActivity.java @@ -55,6 +55,7 @@ import de.k3b.android.androFotoFinder.tagDB.TagSql; import de.k3b.android.androFotoFinder.tagDB.TagsPickerFragment; import de.k3b.android.osmdroid.OsmdroidUtil; +import de.k3b.android.util.PhotoChangeNotifyer; import de.k3b.android.util.PhotoPropertiesMediaFilesScanner; import de.k3b.database.QueryParameter; import de.k3b.io.AlbumFile; @@ -673,8 +674,8 @@ public boolean onOk(List addNames, List removeNames) { protected void onCreate(Bundle savedInstanceState) { Global.debugMemory(mDebugPrefix, "onCreate"); super.onCreate(savedInstanceState); - this.getContentResolver().registerContentObserver(FotoSql.SQL_TABLE_EXTERNAL_CONTENT_URI, true, mMediaObserverDirectory); - this.getContentResolver().registerContentObserver(FotoSql.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE, true, mMediaObserverDirectory); + PhotoChangeNotifyer.registerContentObserver(this, mMediaObserverDirectory); + } protected void onCreateData(Bundle savedInstanceState) { @@ -966,7 +967,7 @@ public void onLowMemory() { @Override protected void onDestroy() { super.onDestroy(); - this.getContentResolver().unregisterContentObserver(mMediaObserverDirectory); + PhotoChangeNotifyer.unregisterContentObserver(this, mMediaObserverDirectory); this.mGalleryQueryParameter.mGalleryContentBaseQuery = null; invalidateDirectories(mDebugPrefix + "#onDestroy"); } From 5066b9539d06ab3882f9c48ce29b827b556511c7 Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Wed, 29 Jan 2020 05:37:33 +0100 Subject: [PATCH 37/37] #155: added feature toggle Global.allow_emulate_ao10 --- app/build.gradle | 2 ++ .../androFotoFinder/AndroFotoFinderApp.java | 6 ++-- .../androFotoFinder/FotoGalleryActivity.java | 2 +- .../k3b/android/androFotoFinder/Global.java | 13 +++++-- .../androFotoFinder/SettingsActivity.java | 16 +++++++-- .../queries/CursorLoaderWithException.java | 2 +- app/src/main/res/xml/preferences.xml | 8 +---- .../main/res/xml/preferences_ao10_test.xml | 34 +++++++++++++++++++ 8 files changed, 65 insertions(+), 18 deletions(-) create mode 100644 app/src/main/res/xml/preferences_ao10_test.xml diff --git a/app/build.gradle b/app/build.gradle index e3031efb..d797ef03 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,6 +19,8 @@ android { minSdkVersion 14 // Android 4.0 Ice Cream Sandwich (API 14); Android 4.4 KitKat (API 19); Android 5.0 Lollipop (API 21); // Android 6.0 Marshmallow (API 23); Android 7.0 Nougat (API 24) + maxSdkVersion 28 // #155: android-10=api29 + targetSdkVersion 21 // non-fdroid release diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java index f516d93c..00ba24d7 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java @@ -94,16 +94,16 @@ public static String getGetTeaserText(Context context, String linkUrlForDetails) public static void setMediaImageDbReplacement(Context context, boolean useMediaImageDbReplacement) { final IMediaRepositoryApi oldMediaDBApi = FotoSql.getMediaDBApi(); - if ((oldMediaDBApi == null) || (Global.useMediaImageDbReplacement != useMediaImageDbReplacement)) { + if ((oldMediaDBApi == null) || (Global.useAo10MediaImageDbReplacement != useMediaImageDbReplacement)) { // menu must be recreated LocalizedActivity.setMustRecreate(); - Global.useMediaImageDbReplacement = useMediaImageDbReplacement; + Global.useAo10MediaImageDbReplacement = useMediaImageDbReplacement; final MediaContentproviderRepository mediaContentproviderRepository = new MediaContentproviderRepository(context); - if (Global.useMediaImageDbReplacement) { + if (Global.useAo10MediaImageDbReplacement) { final SQLiteDatabase writableDatabase = DatabaseHelper.getWritableDatabase(context); final MediaDBRepository mediaDBRepository = new MediaDBRepository(writableDatabase); FotoSql.setMediaDBApi(new MergedMediaRepository(mediaDBRepository, mediaContentproviderRepository)); 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 f14871b1..736386e3 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/FotoGalleryActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/FotoGalleryActivity.java @@ -168,7 +168,7 @@ public boolean onCreateOptionsMenu(Menu menu) { inflater.inflate(R.menu.menu_gallery_non_selected_only, menu); inflater.inflate(R.menu.menu_gallery_non_multiselect, menu); - if (Global.useMediaImageDbReplacement) { + if (Global.useAo10MediaImageDbReplacement) { inflater.inflate(R.menu.menu_ao10, menu); } 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 21a12bb7..426157a8 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/Global.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/Global.java @@ -20,6 +20,7 @@ package de.k3b.android.androFotoFinder; import android.content.Context; +import android.os.Build; import android.os.Environment; import android.util.Log; import android.view.Menu; @@ -124,17 +125,23 @@ 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: Feature-Toggel: set to false while android10 incompatibility is not fixed + public final static boolean allow_emulate_ao10 = !isAndroid10OrAbove() && false; // #155: fix android10 incompatibility // Build.VERSION_CODES.??ANDROID10?? = 29 - //!!! - public static boolean useMediaImageDbReplacement = true; + public static boolean useAo10MediaImageDbReplacement = isAndroid10OrAbove(); -// 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 */ public static final double mapMultiselectionBoxIncreaseMinSizeInDegrees = 0.01; + private static boolean isAndroid10OrAbove() { + // #155: fix android10 incompatibility + // Build.VERSION_CODES.??ANDROID10?? = 29 + return Build.VERSION.SDK_INT >= 29; + } + public static void debugMemory(String modul, String message) { if (Global.debugEnabledMemory) { Runtime r = Runtime.getRuntime(); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java b/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java index bfce1029..64f7ea55 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java @@ -53,6 +53,7 @@ import uk.co.senab.photoview.log.LogManager; public class SettingsActivity extends PreferenceActivity { + private static final String PREF_KEY_USE_MEDIA_IMAGE_DB_REPLACEMENT = "useMediaImageDbReplacement"; private static Boolean sOldEnableNonStandardIptcMediaScanner = null; private SharedPreferences prefsInstance = null; private ListPreference defaultLocalePreference; // #21: Support to change locale at runtime @@ -92,7 +93,9 @@ public static void global2Prefs(Context context) { prefs.putBoolean("xmp_file_schema_long", LibGlobal.preferLongXmpFormat); prefs.putBoolean("mapsForgeEnabled", Global.mapsForgeEnabled); - prefs.putBoolean("useMediaImageDbReplacement", Global.useMediaImageDbReplacement); + if (Global.allow_emulate_ao10) { + prefs.putBoolean(PREF_KEY_USE_MEDIA_IMAGE_DB_REPLACEMENT, Global.useAo10MediaImageDbReplacement); + } prefs.putBoolean("locked", Global.locked); prefs.putString("passwordHash", Global.passwordHash); @@ -172,7 +175,12 @@ public static void prefs2Global(Context context) { LibGlobal.preferLongXmpFormat = getPref(prefs, "xmp_file_schema_long", LibGlobal.preferLongXmpFormat); Global.mapsForgeEnabled = getPref(prefs, "mapsForgeEnabled", Global.mapsForgeEnabled); - AndroFotoFinderApp.setMediaImageDbReplacement(context.getApplicationContext(), getPref(prefs, "useMediaImageDbReplacement", Global.useMediaImageDbReplacement)); + + boolean useAo10MediaImageDbReplacement = Global.useAo10MediaImageDbReplacement; + if (Global.allow_emulate_ao10) { + useAo10MediaImageDbReplacement = getPref(prefs, PREF_KEY_USE_MEDIA_IMAGE_DB_REPLACEMENT, Global.useAo10MediaImageDbReplacement); + } + AndroFotoFinderApp.setMediaImageDbReplacement(context.getApplicationContext(), useAo10MediaImageDbReplacement); Global.imageDetailThumbnailIfBiggerThan = getPref(prefs, "imageDetailThumbnailIfBiggerThan" , Global.imageDetailThumbnailIfBiggerThan); @@ -232,7 +240,9 @@ protected void onCreate(final Bundle savedInstanceState) { } this.addPreferencesFromResource(R.xml.preferences); - this.addPreferencesFromResource(R.xml.preferences); + if (Global.allow_emulate_ao10) { + this.addPreferencesFromResource(R.xml.preferences_ao10_test); + } prefsInstance = PreferenceManager .getDefaultSharedPreferences(this); global2Prefs(this.getApplication()); 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 index 4c68bd79..8e114514 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/CursorLoaderWithException.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/CursorLoaderWithException.java @@ -63,7 +63,7 @@ public Cursor loadInBackground() { try { Cursor cursor; - cursor = FotoSql.getMediaDBApi().createCursorForQuery(null, "loadader", this.query, null, mCancellationSignal); + cursor = FotoSql.getMediaDBApi().createCursorForQuery(null, "loader", this.query, null, mCancellationSignal); if (cursor != null) { try { // Ensure the cursor window is filled. diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 343f0a22..85fd213e 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -1,6 +1,6 @@ + + + + + + + +