mirror of
				https://github.com/commons-app/apps-android-commons.git
				synced 2025-10-31 06:43:56 +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 | ||||
| 
 | ||||
| class FileProcessor | ||||
|     @Inject | ||||
|     constructor( | ||||
|         private val context: Context, | ||||
|         private val contentResolver: ContentResolver, | ||||
|         private val gpsCategoryModel: GpsCategoryModel, | ||||
|         private val depictsModel: DepictModel, | ||||
|         @param:Named("default_preferences") private val defaultKvStore: JsonKvStore, | ||||
|         private val apiCall: CategoryApi, | ||||
|         private val okHttpJsonApiClient: OkHttpJsonApiClient, | ||||
|     ) { | ||||
|         private val compositeDisposable = CompositeDisposable() | ||||
| @Inject | ||||
| constructor( | ||||
|     private val context: Context, | ||||
|     private val contentResolver: ContentResolver, | ||||
|     private val gpsCategoryModel: GpsCategoryModel, | ||||
|     private val depictsModel: DepictModel, | ||||
|     @param:Named("default_preferences") private val defaultKvStore: JsonKvStore, | ||||
|     private val apiCall: CategoryApi, | ||||
|     private val okHttpJsonApiClient: OkHttpJsonApiClient, | ||||
| ) { | ||||
|     private val compositeDisposable = CompositeDisposable() | ||||
| 
 | ||||
|         fun cleanup() { | ||||
|             compositeDisposable.clear() | ||||
|         } | ||||
|     fun cleanup() { | ||||
|         compositeDisposable.clear() | ||||
|     } | ||||
| 
 | ||||
|         /** | ||||
|          * Processes filePath coordinates, either from EXIF data or user location | ||||
|          */ | ||||
|         fun processFileCoordinates( | ||||
|             similarImageInterface: SimilarImageInterface, | ||||
|             filePath: String?, | ||||
|             inAppPictureLocation: LatLng?, | ||||
|         ): ImageCoordinates { | ||||
|             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?) { | ||||
|     /** | ||||
|      * Processes filePath coordinates, either from EXIF data or user location | ||||
|      */ | ||||
|     fun processFileCoordinates( | ||||
|         similarImageInterface: SimilarImageInterface, | ||||
|         filePath: String?, | ||||
|         inAppPictureLocation: LatLng?, | ||||
|     ): ImageCoordinates { | ||||
|         val 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, 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) | ||||
|                 ExifInterface(filePath!!) | ||||
|             } catch (e: IOException) { | ||||
|                 Timber.e(e) | ||||
|                 try { | ||||
|                     ImageCoordinates(file.absolutePath, null) | ||||
|                 } catch (ex: IOException) { | ||||
|                     Timber.e(ex) | ||||
|                     null | ||||
|                 } | ||||
|                 null | ||||
|             } | ||||
| 
 | ||||
|         /** | ||||
|          * Initiates retrieval of image coordinates or user coordinates, and caching of coordinates. Then | ||||
|          * initiates the calls to MediaWiki API through an instance of CategoryApi. | ||||
|          * | ||||
|          * @param imageCoordinates | ||||
|          */ | ||||
|         fun prePopulateCategoriesAndDepictionsBy(imageCoordinates: ImageCoordinates) { | ||||
|             requireNotNull(imageCoordinates.decimalCoords) | ||||
|             compositeDisposable.add( | ||||
|                 apiCall | ||||
|                     .request(imageCoordinates.decimalCoords!!) | ||||
|                     .subscribeOn(Schedulers.io()) | ||||
|                     .observeOn(Schedulers.io()) | ||||
|                     .subscribe( | ||||
|                         gpsCategoryModel::setCategoriesFromLocation, | ||||
|                         { | ||||
|                             Timber.e(it) | ||||
|                             gpsCategoryModel.clear() | ||||
|                         }, | ||||
|                     ), | ||||
|         // 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 | ||||
|     } | ||||
| 
 | ||||
|             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) | ||||
| 
 | ||||
|         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) | ||||
|     /** | ||||
|      * Initiates retrieval of image coordinates or user coordinates, and caching of coordinates. Then | ||||
|      * initiates the calls to MediaWiki API through an instance of CategoryApi. | ||||
|      * | ||||
|      * @param imageCoordinates | ||||
|      */ | ||||
|     fun prePopulateCategoriesAndDepictionsBy(imageCoordinates: ImageCoordinates) { | ||||
|         requireNotNull(imageCoordinates.decimalCoords) | ||||
|         compositeDisposable.add( | ||||
|             apiCall | ||||
|                 .request(imageCoordinates.decimalCoords!!) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(Schedulers.io()) | ||||
|                 .subscribe( | ||||
|                     { depictsModel.nearbyPlaces.offer(it) }, | ||||
|                     { Timber.e(it) }, | ||||
|                 ) | ||||
|                     gpsCategoryModel::setCategoriesFromLocation, | ||||
|                     { | ||||
|                         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
	
	 sonalyadav
						sonalyadav