mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-29 22:03:55 +01:00
fixed warning
This commit is contained in:
parent
68c24c47e3
commit
6e929e3786
1 changed files with 190 additions and 189 deletions
|
|
@ -32,204 +32,205 @@ private const val RADIUS_STEP_SIZE_IN_METRES = 100
|
||||||
private const val MIN_NEARBY_RESULTS = 5
|
private const val MIN_NEARBY_RESULTS = 5
|
||||||
|
|
||||||
class FileProcessor
|
class FileProcessor
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val contentResolver: ContentResolver,
|
private val contentResolver: ContentResolver,
|
||||||
private val gpsCategoryModel: GpsCategoryModel,
|
private val gpsCategoryModel: GpsCategoryModel,
|
||||||
private val depictsModel: DepictModel,
|
private val depictsModel: DepictModel,
|
||||||
@param:Named("default_preferences") private val defaultKvStore: JsonKvStore,
|
@param:Named("default_preferences") private val defaultKvStore: JsonKvStore,
|
||||||
private val apiCall: CategoryApi,
|
private val apiCall: CategoryApi,
|
||||||
private val okHttpJsonApiClient: OkHttpJsonApiClient,
|
private val okHttpJsonApiClient: OkHttpJsonApiClient,
|
||||||
) {
|
) {
|
||||||
private val compositeDisposable = CompositeDisposable()
|
private val compositeDisposable = CompositeDisposable()
|
||||||
|
|
||||||
fun cleanup() {
|
fun cleanup() {
|
||||||
compositeDisposable.clear()
|
compositeDisposable.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes filePath coordinates, either from EXIF data or user location
|
* Processes filePath coordinates, either from EXIF data or user location
|
||||||
*/
|
*/
|
||||||
fun processFileCoordinates(
|
fun processFileCoordinates(
|
||||||
similarImageInterface: SimilarImageInterface,
|
similarImageInterface: SimilarImageInterface,
|
||||||
filePath: String?,
|
filePath: String?,
|
||||||
inAppPictureLocation: LatLng?,
|
inAppPictureLocation: LatLng?,
|
||||||
): ImageCoordinates {
|
): ImageCoordinates {
|
||||||
val exifInterface: ExifInterface? =
|
val exifInterface: ExifInterface? =
|
||||||
try {
|
|
||||||
ExifInterface(filePath!!)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
Timber.e(e)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
// Redact EXIF data as indicated in preferences.
|
|
||||||
redactExifTags(exifInterface, getExifTagsToRedact())
|
|
||||||
Timber.d("Calling GPSExtractor")
|
|
||||||
val originalImageCoordinates = ImageCoordinates(exifInterface, inAppPictureLocation)
|
|
||||||
if (originalImageCoordinates.decimalCoords == null) {
|
|
||||||
// Find other photos taken around the same time which has gps coordinates
|
|
||||||
findOtherImages(
|
|
||||||
File(filePath),
|
|
||||||
similarImageInterface,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
prePopulateCategoriesAndDepictionsBy(originalImageCoordinates)
|
|
||||||
}
|
|
||||||
return originalImageCoordinates
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets EXIF Tags from preferences to be redacted.
|
|
||||||
*
|
|
||||||
* @return tags to be redacted
|
|
||||||
*/
|
|
||||||
fun getExifTagsToRedact(): Set<String> {
|
|
||||||
val prefManageEXIFTags =
|
|
||||||
defaultKvStore.getStringSet(Prefs.MANAGED_EXIF_TAGS) ?: emptySet()
|
|
||||||
val redactTags: Set<String> =
|
|
||||||
context.resources.getStringArray(R.array.pref_exifTag_values).toSet()
|
|
||||||
return redactTags - prefManageEXIFTags
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redacts EXIF metadata as indicated in preferences.
|
|
||||||
*
|
|
||||||
* @param exifInterface ExifInterface object
|
|
||||||
* @param redactTags tags to be redacted
|
|
||||||
*/
|
|
||||||
fun redactExifTags(
|
|
||||||
exifInterface: ExifInterface?,
|
|
||||||
redactTags: Set<String>,
|
|
||||||
) {
|
|
||||||
compositeDisposable.add(
|
|
||||||
Observable
|
|
||||||
.fromIterable(redactTags)
|
|
||||||
.flatMap { Observable.fromArray(*FileMetadataUtils.getTagsFromPref(it)) }
|
|
||||||
.subscribe(
|
|
||||||
{ redactTag(exifInterface, it) },
|
|
||||||
{ Timber.d(it) },
|
|
||||||
{ save(exifInterface) },
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun save(exifInterface: ExifInterface?) {
|
|
||||||
try {
|
try {
|
||||||
exifInterface?.saveAttributes()
|
ExifInterface(filePath!!)
|
||||||
} catch (e: IOException) {
|
|
||||||
Timber.w("EXIF redaction failed: %s", e.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun redactTag(
|
|
||||||
exifInterface: ExifInterface?,
|
|
||||||
tag: String,
|
|
||||||
) {
|
|
||||||
Timber.d("Checking for tag: %s", tag)
|
|
||||||
exifInterface
|
|
||||||
?.getAttribute(tag)
|
|
||||||
?.takeIf { it.isNotEmpty() }
|
|
||||||
?.let { attributeName ->
|
|
||||||
exifInterface.setAttribute(tag, null).also {
|
|
||||||
Timber.d("Exif tag $tag with value $attributeName redacted.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find other images around the same location that were taken within the last 20 sec
|
|
||||||
*
|
|
||||||
* @param originalImageCoordinates
|
|
||||||
* @param fileBeingProcessed
|
|
||||||
* @param similarImageInterface
|
|
||||||
*/
|
|
||||||
private fun findOtherImages(
|
|
||||||
fileBeingProcessed: File,
|
|
||||||
similarImageInterface: SimilarImageInterface,
|
|
||||||
) {
|
|
||||||
val oneHundredAndTwentySeconds = 120 * 1000L
|
|
||||||
// Time when the original image was created
|
|
||||||
val timeOfCreation = fileBeingProcessed.lastModified()
|
|
||||||
LongRange
|
|
||||||
val timeOfCreationRange =
|
|
||||||
timeOfCreation - oneHundredAndTwentySeconds..timeOfCreation + oneHundredAndTwentySeconds
|
|
||||||
fileBeingProcessed.parentFile
|
|
||||||
.listFiles()
|
|
||||||
.asSequence()
|
|
||||||
.filter { it.lastModified() in timeOfCreationRange }
|
|
||||||
.map { Pair(it, readImageCoordinates(it)) }
|
|
||||||
.firstOrNull { it.second?.decimalCoords != null }
|
|
||||||
?.let { fileCoordinatesPair ->
|
|
||||||
similarImageInterface.showSimilarImageFragment(
|
|
||||||
fileBeingProcessed.path,
|
|
||||||
fileCoordinatesPair.first.absolutePath,
|
|
||||||
fileCoordinatesPair.second,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun readImageCoordinates(file: File) =
|
|
||||||
try {
|
|
||||||
/* Used null location as location for similar images captured before is not available
|
|
||||||
in case it is not present in the EXIF. */
|
|
||||||
ImageCoordinates(contentResolver.openInputStream(Uri.fromFile(file))!!, null)
|
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
try {
|
null
|
||||||
ImageCoordinates(file.absolutePath, null)
|
|
||||||
} catch (ex: IOException) {
|
|
||||||
Timber.e(ex)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// Redact EXIF data as indicated in preferences.
|
||||||
/**
|
redactExifTags(exifInterface, getExifTagsToRedact())
|
||||||
* Initiates retrieval of image coordinates or user coordinates, and caching of coordinates. Then
|
Timber.d("Calling GPSExtractor")
|
||||||
* initiates the calls to MediaWiki API through an instance of CategoryApi.
|
val originalImageCoordinates = ImageCoordinates(exifInterface, inAppPictureLocation)
|
||||||
*
|
if (originalImageCoordinates.decimalCoords == null) {
|
||||||
* @param imageCoordinates
|
// Find other photos taken around the same time which has gps coordinates
|
||||||
*/
|
findOtherImages(
|
||||||
fun prePopulateCategoriesAndDepictionsBy(imageCoordinates: ImageCoordinates) {
|
File(filePath),
|
||||||
requireNotNull(imageCoordinates.decimalCoords)
|
similarImageInterface,
|
||||||
compositeDisposable.add(
|
|
||||||
apiCall
|
|
||||||
.request(imageCoordinates.decimalCoords!!)
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(Schedulers.io())
|
|
||||||
.subscribe(
|
|
||||||
gpsCategoryModel::setCategoriesFromLocation,
|
|
||||||
{
|
|
||||||
Timber.e(it)
|
|
||||||
gpsCategoryModel.clear()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
prePopulateCategoriesAndDepictionsBy(originalImageCoordinates)
|
||||||
|
}
|
||||||
|
return originalImageCoordinates
|
||||||
|
}
|
||||||
|
|
||||||
compositeDisposable.add(
|
/**
|
||||||
suggestNearbyDepictions(imageCoordinates),
|
* Gets EXIF Tags from preferences to be redacted.
|
||||||
)
|
*
|
||||||
|
* @return tags to be redacted
|
||||||
|
*/
|
||||||
|
fun getExifTagsToRedact(): Set<String> {
|
||||||
|
val prefManageEXIFTags =
|
||||||
|
defaultKvStore.getStringSet(Prefs.MANAGED_EXIF_TAGS) ?: emptySet<String>()
|
||||||
|
val redactTags: Set<String> =
|
||||||
|
context.resources.getStringArray(R.array.pref_exifTag_values).toSet()
|
||||||
|
return redactTags - prefManageEXIFTags
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redacts EXIF metadata as indicated in preferences.
|
||||||
|
*
|
||||||
|
* @param exifInterface ExifInterface object
|
||||||
|
* @param redactTags tags to be redacted
|
||||||
|
*/
|
||||||
|
fun redactExifTags(
|
||||||
|
exifInterface: ExifInterface?,
|
||||||
|
redactTags: Set<String>,
|
||||||
|
) {
|
||||||
|
compositeDisposable.add(
|
||||||
|
Observable
|
||||||
|
.fromIterable(redactTags)
|
||||||
|
.flatMap { Observable.fromArray(*FileMetadataUtils.getTagsFromPref(it)) }
|
||||||
|
.subscribe(
|
||||||
|
{ redactTag(exifInterface, it) },
|
||||||
|
{ Timber.d(it) },
|
||||||
|
{ save(exifInterface) },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun save(exifInterface: ExifInterface?) {
|
||||||
|
try {
|
||||||
|
exifInterface?.saveAttributes()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Timber.w("EXIF redaction failed: %s", e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun redactTag(
|
||||||
|
exifInterface: ExifInterface?,
|
||||||
|
tag: String,
|
||||||
|
) {
|
||||||
|
Timber.d("Checking for tag: %s", tag)
|
||||||
|
exifInterface
|
||||||
|
?.getAttribute(tag)
|
||||||
|
?.takeIf { it.isNotEmpty() }
|
||||||
|
?.let { attributeName ->
|
||||||
|
exifInterface.setAttribute(tag, "").also {
|
||||||
|
Timber.d("EXIF tag $tag removed or set to empty.")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find other images around the same location that were taken within the last 20 sec
|
||||||
|
*
|
||||||
|
* @param originalImageCoordinates
|
||||||
|
* @param fileBeingProcessed
|
||||||
|
* @param similarImageInterface
|
||||||
|
*/
|
||||||
|
private fun findOtherImages(
|
||||||
|
fileBeingProcessed: File,
|
||||||
|
similarImageInterface: SimilarImageInterface,
|
||||||
|
) {
|
||||||
|
val oneHundredAndTwentySeconds = 120 * 1000L
|
||||||
|
// Time when the original image was created
|
||||||
|
val timeOfCreation = fileBeingProcessed.lastModified()
|
||||||
|
LongRange
|
||||||
|
val timeOfCreationRange =
|
||||||
|
timeOfCreation - oneHundredAndTwentySeconds..timeOfCreation + oneHundredAndTwentySeconds
|
||||||
|
fileBeingProcessed.parentFile
|
||||||
|
.listFiles()
|
||||||
|
.asSequence()
|
||||||
|
.filter { it.lastModified() in timeOfCreationRange }
|
||||||
|
.map { Pair(it, readImageCoordinates(it)) }
|
||||||
|
.firstOrNull { it.second?.decimalCoords != null }
|
||||||
|
?.let { fileCoordinatesPair ->
|
||||||
|
similarImageInterface.showSimilarImageFragment(
|
||||||
|
fileBeingProcessed.path,
|
||||||
|
fileCoordinatesPair.first.absolutePath,
|
||||||
|
fileCoordinatesPair.second,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readImageCoordinates(file: File) =
|
||||||
|
try {
|
||||||
|
/* Used null location as location for similar images captured before is not available
|
||||||
|
in case it is not present in the EXIF. */
|
||||||
|
ImageCoordinates(contentResolver.openInputStream(Uri.fromFile(file))!!, null)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Timber.e(e)
|
||||||
|
try {
|
||||||
|
ImageCoordinates(file.absolutePath, null)
|
||||||
|
} catch (ex: IOException) {
|
||||||
|
Timber.e(ex)
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val radiiProgressionInMetres =
|
/**
|
||||||
(DEFAULT_SUGGESTION_RADIUS_IN_METRES..MAX_SUGGESTION_RADIUS_IN_METRES step RADIUS_STEP_SIZE_IN_METRES)
|
* Initiates retrieval of image coordinates or user coordinates, and caching of coordinates. Then
|
||||||
|
* initiates the calls to MediaWiki API through an instance of CategoryApi.
|
||||||
private fun suggestNearbyDepictions(imageCoordinates: ImageCoordinates): Disposable =
|
*
|
||||||
Observable
|
* @param imageCoordinates
|
||||||
.fromIterable(radiiProgressionInMetres.map { it / 1000.0 })
|
*/
|
||||||
.concatMap {
|
fun prePopulateCategoriesAndDepictionsBy(imageCoordinates: ImageCoordinates) {
|
||||||
Observable.fromCallable {
|
requireNotNull(imageCoordinates.decimalCoords)
|
||||||
okHttpJsonApiClient.getNearbyPlaces(
|
compositeDisposable.add(
|
||||||
imageCoordinates.latLng!!,
|
apiCall
|
||||||
Locale.getDefault().language,
|
.request(imageCoordinates.decimalCoords!!)
|
||||||
it,
|
.subscribeOn(Schedulers.io())
|
||||||
)
|
.observeOn(Schedulers.io())
|
||||||
}
|
|
||||||
}.subscribeOn(Schedulers.io())
|
|
||||||
.filter { it.size >= MIN_NEARBY_RESULTS }
|
|
||||||
.take(1)
|
|
||||||
.subscribe(
|
.subscribe(
|
||||||
{ depictsModel.nearbyPlaces.offer(it) },
|
gpsCategoryModel::setCategoriesFromLocation,
|
||||||
{ Timber.e(it) },
|
{
|
||||||
)
|
Timber.e(it)
|
||||||
|
gpsCategoryModel.clear()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
compositeDisposable.add(
|
||||||
|
suggestNearbyDepictions(imageCoordinates),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val radiiProgressionInMetres =
|
||||||
|
(DEFAULT_SUGGESTION_RADIUS_IN_METRES..MAX_SUGGESTION_RADIUS_IN_METRES step RADIUS_STEP_SIZE_IN_METRES)
|
||||||
|
|
||||||
|
private fun suggestNearbyDepictions(imageCoordinates: ImageCoordinates): Disposable =
|
||||||
|
Observable
|
||||||
|
.fromIterable(radiiProgressionInMetres.map { it / 1000.0 })
|
||||||
|
.concatMap {
|
||||||
|
Observable.fromCallable {
|
||||||
|
okHttpJsonApiClient.getNearbyPlaces(
|
||||||
|
imageCoordinates.latLng!!,
|
||||||
|
Locale.getDefault().language,
|
||||||
|
it,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.subscribeOn(Schedulers.io())
|
||||||
|
.filter { it.size >= MIN_NEARBY_RESULTS }
|
||||||
|
.take(1)
|
||||||
|
.subscribe(
|
||||||
|
{ depictsModel.nearbyPlaces.offer(it) },
|
||||||
|
{ Timber.e(it) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue