Image Loader Improvements (#4516)

This commit is contained in:
Aditya-Srivastav 2021-07-21 08:57:25 +05:30 committed by Aditya Srivastava
parent 89fd4f4b1f
commit 1a5bb1f622
5 changed files with 144 additions and 88 deletions

View file

@ -50,39 +50,37 @@ abstract class UploadedStatusDao {
/**
* Asynchronous insert into uploaded status table.
*/
fun insertUploaded(uploadedStatus: UploadedStatus) = runBlocking {
async {
uploadedStatus.lastUpdated = Calendar.getInstance().time as Date?
insert(uploadedStatus)
}.await()
suspend fun insertUploaded(uploadedStatus: UploadedStatus) {
uploadedStatus.lastUpdated = Calendar.getInstance().time as Date?
insert(uploadedStatus)
}
/**
* Asynchronous delete from uploaded status table.
*/
fun deleteUploaded(uploadedStatus: UploadedStatus) = runBlocking {
async { delete(uploadedStatus) }
suspend fun deleteUploaded(uploadedStatus: UploadedStatus) {
delete(uploadedStatus)
}
/**
* Asynchronous update entry in uploaded status table.
*/
fun updateUploaded(uploadedStatus: UploadedStatus) = runBlocking {
async { update(uploadedStatus) }
suspend fun updateUploaded(uploadedStatus: UploadedStatus) {
update(uploadedStatus)
}
/**
* Asynchronous image sha1 query.
*/
fun getUploadedFromImageSHA1(imageSHA1: String) = runBlocking<UploadedStatus?> {
async { getFromImageSHA1(imageSHA1) }.await()
suspend fun getUploadedFromImageSHA1(imageSHA1: String):UploadedStatus {
return getFromImageSHA1(imageSHA1)
}
/**
* Asynchronous modified image sha1 query.
*/
fun getUploadedFromModifiedImageSHA1(modifiedImageSHA1: String) = runBlocking<UploadedStatus?> {
async { getFromModifiedImageSHA1(modifiedImageSHA1) }.await()
suspend fun getUploadedFromModifiedImageSHA1(modifiedImageSHA1: String):UploadedStatus {
return getFromModifiedImageSHA1(modifiedImageSHA1)
}
}

View file

@ -76,7 +76,7 @@ class ImageAdapter(
else {
holder.itemUnselected();
}
Glide.with(context).load(image.uri).into(holder.image)
Glide.with(context).load(image.uri).thumbnail(0.3f).into(holder.image)
imageLoader.queryAndSetView(holder,image)
holder.itemView.setOnClickListener {
selectOrRemoveImage(holder, position)
@ -99,8 +99,8 @@ class ImageAdapter(
if(holder.isItemUploaded()){
Toast.makeText(context,"Already Uploaded image", Toast.LENGTH_SHORT).show()
} else {
selectedImages.add(images[position])
notifyItemChanged(position, ImageSelectedOrUpdated())
selectedImages.add(images[position])
notifyItemChanged(position, ImageSelectedOrUpdated())
}
}
imageSelectListener.onSelectedImagesChanged(selectedImages)

View file

@ -148,4 +148,12 @@ class ImageFragment: CommonsDaggerSupportFragment() {
return 3
// todo change span count depending on the device orientation and other factos.
}
/**
* OnDestroy Cleanup the imageLoader coroutine.
*/
override fun onDestroy() {
imageLoader?.cleanUP()
super.onDestroy()
}
}

View file

@ -11,10 +11,7 @@ import fr.free.nrw.commons.filepicker.PickedFiles
import fr.free.nrw.commons.media.MediaClient
import fr.free.nrw.commons.upload.FileProcessor
import fr.free.nrw.commons.upload.FileUtilsWrapper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.*
import timber.log.Timber
import java.io.IOException
import java.net.UnknownHostException
@ -57,9 +54,17 @@ class ImageLoader @Inject constructor(
/**
* Maps to facilitate image query.
*/
private var mapImageSHA1: HashMap<Image, String> = HashMap()
private var mapModifiedImageSHA1: HashMap<Image, String> = HashMap()
private var mapHolderImage : HashMap<ImageViewHolder, Image> = HashMap()
private var mapResult: HashMap<String, Result> = HashMap()
private var mapImageSHA1: HashMap<Uri, String> = HashMap()
/**
* Coroutine Dispatchers and Scope.
*/
private var defaultDispatcher = Dispatchers.Default
private var ioDispatcher = Dispatchers.IO
private val scope = MainScope()
/**
* Query image and setUp the view.
@ -72,42 +77,43 @@ class ImageLoader @Inject constructor(
mapHolderImage[holder] = image
holder.itemNotUploaded()
CoroutineScope(Dispatchers.Main).launch {
scope.launch {
var result : Result = Result.NOTFOUND
withContext(Dispatchers.Default) {
var result: Result = Result.NOTFOUND
if (mapHolderImage[holder] != image) {
return@launch
}
val imageSHA1 = getImageSHA1(image.uri)
val uploadedStatus = getFromUploaded(imageSHA1)
val sha1 = uploadedStatus?.let {
result = getResultFromUploadedStatus(uploadedStatus)
uploadedStatus.modifiedImageSHA1
} ?: run {
if (mapHolderImage[holder] == image) {
val imageSHA1 = getImageSHA1(image.uri)
val uploadedStatus = uploadedStatusDao.getUploadedFromImageSHA1(imageSHA1)
getSHA1(image)
} else {
""
}
}
val sha1 = uploadedStatus?.let {
result = getResultFromUploadedStatus(uploadedStatus)
uploadedStatus.modifiedImageSHA1
} ?: run {
if(mapHolderImage[holder] == image) {
getSHA1(image)
} else {
""
}
}
if (mapHolderImage[holder] != image) {
return@launch
}
if (mapHolderImage[holder] == image &&
result in arrayOf(Result.NOTFOUND, Result.INVALID) &&
sha1.isNotEmpty()) {
// Query original image.
result = querySHA1(imageSHA1)
if( result is Result.TRUE ) {
// Original image found.
insertIntoUploaded(imageSHA1, sha1, result is Result.TRUE, false)
}
else {
// Original image not found, query modified image.
result = querySHA1(sha1)
if (result != Result.ERROR) {
insertIntoUploaded(imageSHA1, sha1, false, result is Result.TRUE)
}
}
if (result in arrayOf(Result.NOTFOUND, Result.INVALID) && sha1.isNotEmpty()) {
// Query original image.
result = querySHA1(imageSHA1)
if (result is Result.TRUE) {
// Original image found.
insertIntoUploaded(imageSHA1, sha1, result is Result.TRUE, false)
} else {
// Original image not found, query modified image.
result = querySHA1(sha1)
if (result != Result.ERROR) {
insertIntoUploaded(imageSHA1, sha1, false, result is Result.TRUE)
}
}
}
@ -122,25 +128,27 @@ class ImageLoader @Inject constructor(
*
* @return Query result.
*/
private fun querySHA1(SHA1: String): Result {
mapResult[SHA1]?.let{
return it
}
var result : Result = Result.FALSE
try {
if (mediaClient.checkFileExistsUsingSha(SHA1).blockingGet()) {
mapResult[SHA1] = Result.TRUE
result = Result.TRUE
private suspend fun querySHA1(SHA1: String): Result {
return withContext(ioDispatcher) {
mapResult[SHA1]?.let {
return@withContext it
}
} catch (e: Exception) {
if (e is UnknownHostException) {
// Handle no network connection.
Timber.e(e, "Network Connection Error")
var result: Result = Result.FALSE
try {
if (mediaClient.checkFileExistsUsingSha(SHA1).blockingGet()) {
mapResult[SHA1] = Result.TRUE
result = Result.TRUE
}
} catch (e: Exception) {
if (e is UnknownHostException) {
// Handle no network connection.
Timber.e(e, "Network Connection Error")
}
result = Result.ERROR
e.printStackTrace()
}
result = Result.ERROR
e.printStackTrace()
} finally {
return result
result
}
}
@ -149,27 +157,48 @@ class ImageLoader @Inject constructor(
*
* @return sha1 of the image
*/
private fun getSHA1(image: Image): String {
mapImageSHA1[image]?.let{
private suspend fun getSHA1(image: Image): String {
mapModifiedImageSHA1[image]?.let{
return it
}
val sha1 = generateModifiedSHA1(image);
mapImageSHA1[image] = sha1;
mapModifiedImageSHA1[image] = sha1;
return sha1;
}
/**
* Get the uploaded status entry from the database.
*/
private suspend fun getFromUploaded(imageSha1:String): UploadedStatus?{
return uploadedStatusDao.getUploadedFromImageSHA1(imageSha1)
}
/**
* Insert into uploaded status table.
*/
private fun insertIntoUploaded(imageSha1:String, modifiedImageSha1:String, imageResult:Boolean, modifiedImageResult: Boolean){
uploadedStatusDao.insertUploaded(UploadedStatus(imageSha1, modifiedImageSha1, imageResult, modifiedImageResult))
private suspend fun insertIntoUploaded(imageSha1:String, modifiedImageSha1:String, imageResult:Boolean, modifiedImageResult: Boolean){
uploadedStatusDao.insertUploaded(
UploadedStatus(
imageSha1,
modifiedImageSha1,
imageResult,
modifiedImageResult
)
)
}
/**
* Get image sha1 from uri, used to retrieve the original image sha1.
*/
private fun getImageSHA1(uri: Uri): String {
return fileUtilsWrapper.getSHA1(context.contentResolver.openInputStream(uri))
private suspend fun getImageSHA1(uri: Uri): String {
return withContext(ioDispatcher) {
mapImageSHA1[uri]?.let{
return@withContext it
}
val result = fileUtilsWrapper.getSHA1(context.contentResolver.openInputStream(uri))
mapImageSHA1[uri] = result
result
}
}
/**
@ -194,18 +223,28 @@ class ImageLoader @Inject constructor(
*
* @return modified sha1
*/
private fun generateModifiedSHA1(image: Image) : String {
val uploadableFile = PickedFiles.pickedExistingPicture(context, image.uri)
val exifInterface: ExifInterface? = try {
ExifInterface(uploadableFile.file!!)
} catch (e: IOException) {
Timber.e(e)
null
private suspend fun generateModifiedSHA1(image: Image) : String {
return withContext(defaultDispatcher) {
val uploadableFile = PickedFiles.pickedExistingPicture(context, image.uri)
val exifInterface: ExifInterface? = try {
ExifInterface(uploadableFile.file!!)
} catch (e: IOException) {
Timber.e(e)
null
}
fileProcessor.redactExifTags(exifInterface, fileProcessor.getExifTagsToRedact())
val sha1 =
fileUtilsWrapper.getSHA1(fileUtilsWrapper.getFileInputStream(uploadableFile.filePath))
uploadableFile.file.delete()
sha1
}
fileProcessor.redactExifTags(exifInterface, fileProcessor.getExifTagsToRedact())
val sha1 = fileUtilsWrapper.getSHA1(fileUtilsWrapper.getFileInputStream(uploadableFile.filePath))
uploadableFile.file.delete()
return sha1
}
/**
* CleanUp function.
*/
fun cleanUP() {
scope.cancel()
}
/**

View file

@ -28,9 +28,11 @@ import fr.free.nrw.commons.upload.UploadClient
import fr.free.nrw.commons.upload.UploadResult
import fr.free.nrw.commons.wikidata.WikidataEditService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.util.*
@ -408,7 +410,16 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
contribution.contentUri?.let {
val imageSha1 = fileUtilsWrapper.getSHA1(appContext.contentResolver.openInputStream(it))
val modifiedSha1 = fileUtilsWrapper.getSHA1(fileUtilsWrapper.getFileInputStream(contribution.localUri?.path))
uploadedStatusDao.insertUploaded(UploadedStatus(imageSha1, modifiedSha1, imageSha1 == modifiedSha1, true));
MainScope().launch {
uploadedStatusDao.insertUploaded(
UploadedStatus(
imageSha1,
modifiedSha1,
imageSha1 == modifiedSha1,
true
)
);
}
}
}