mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-27 04:43:54 +01:00
* #3408 Refactoring the FileProcessor and GPSExtractor classes - refactor FileProcessor * #3408 Refactoring the FileProcessor and GPSExtractor classes - refactor and rename GpsExtractor * #3408 Refactoring the FileProcessor and GPSExtractor classes - convert ImageCoordinates to kotlin * #3408 Refactoring the FileProcessor and GPSExtractor classes - convert FileProcessor to kotlin * #3408 Refactoring the FileProcessor and GPSExtractor classes - minor reformatting * #3408 Refactoring the FileProcessor and GPSExtractor classes - fix compilation and naming issues * #3408 Refactoring the FileProcessor and GPSExtractor classes - remove empty test * #3408 Refactoring the FileProcessor and GPSExtractor classes - set coordinates for upload item if user chooses it
This commit is contained in:
parent
efc6fa6211
commit
fb51fc618a
17 changed files with 450 additions and 834 deletions
|
|
@ -2,26 +2,13 @@ package fr.free.nrw.commons.di;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.ContentProviderClient;
|
import android.content.ContentProviderClient;
|
||||||
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
|
||||||
import androidx.collection.LruCache;
|
import androidx.collection.LruCache;
|
||||||
import androidx.room.Room;
|
import androidx.room.Room;
|
||||||
|
|
||||||
import com.github.varunpant.quadtree.QuadTree;
|
import com.github.varunpant.quadtree.QuadTree;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
import org.wikipedia.AppAdapter;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import fr.free.nrw.commons.BuildConfig;
|
import fr.free.nrw.commons.BuildConfig;
|
||||||
|
|
@ -41,6 +28,14 @@ import fr.free.nrw.commons.wikidata.WikidataEditListenerImpl;
|
||||||
import io.reactivex.Scheduler;
|
import io.reactivex.Scheduler;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import org.wikipedia.AppAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Dependency Provider class for Commons Android.
|
* The Dependency Provider class for Commons Android.
|
||||||
|
|
@ -244,4 +239,9 @@ public class CommonsApplicationModule {
|
||||||
public ContributionDao providesContributionsDao() {
|
public ContributionDao providesContributionsDao() {
|
||||||
return appDatabase.getContributionDao();
|
return appDatabase.getContributionDao();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
public ContentResolver providesContentResolver(Context context){
|
||||||
|
return context.getContentResolver();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package fr.free.nrw.commons.repository;
|
package fr.free.nrw.commons.repository;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.upload.ImageCoordinates;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -202,4 +203,8 @@ public class UploadRemoteDataSource {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex) {
|
||||||
|
uploadModel.useSimilarPictureCoordinates(imageCoordinates, uploadItemIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package fr.free.nrw.commons.repository;
|
package fr.free.nrw.commons.repository;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.upload.ImageCoordinates;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -271,4 +272,8 @@ public class UploadRepository {
|
||||||
public Place checkNearbyPlaces(double decLatitude, double decLongitude) {
|
public Place checkNearbyPlaces(double decLatitude, double decLongitude) {
|
||||||
return remoteDataSource.getNearbyPlaces(decLatitude, decLongitude);
|
return remoteDataSource.getNearbyPlaces(decLatitude, decLongitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex) {
|
||||||
|
remoteDataSource.useSimilarPictureCoordinates(imageCoordinates, uploadItemIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,245 +0,0 @@
|
||||||
package fr.free.nrw.commons.upload;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.net.Uri;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.exifinterface.media.ExifInterface;
|
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.R;
|
|
||||||
import fr.free.nrw.commons.caching.CacheController;
|
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
|
||||||
import fr.free.nrw.commons.mwapi.CategoryApi;
|
|
||||||
import fr.free.nrw.commons.settings.Prefs;
|
|
||||||
import fr.free.nrw.commons.upload.SimilarImageDialogFragment.Callback;
|
|
||||||
import io.reactivex.Observable;
|
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
|
||||||
import io.reactivex.disposables.Disposable;
|
|
||||||
import io.reactivex.schedulers.Schedulers;
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processing of the image filePath that is about to be uploaded via ShareActivity is done here
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
public class FileProcessor implements Callback {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
CacheController cacheController;
|
|
||||||
@Inject
|
|
||||||
GpsCategoryModel gpsCategoryModel;
|
|
||||||
@Inject
|
|
||||||
CategoryApi apiCall;
|
|
||||||
@Inject
|
|
||||||
@Named("default_preferences")
|
|
||||||
JsonKvStore defaultKvStore;
|
|
||||||
private String filePath;
|
|
||||||
private ContentResolver contentResolver;
|
|
||||||
private GPSExtractor imageObj;
|
|
||||||
private String decimalCoords;
|
|
||||||
private ExifInterface exifInterface;
|
|
||||||
private boolean haveCheckedForOtherImages = false;
|
|
||||||
private GPSExtractor tempImageObj;
|
|
||||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public FileProcessor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cleanup() {
|
|
||||||
compositeDisposable.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void initFileDetails(@NonNull String filePath, ContentResolver contentResolver) {
|
|
||||||
this.filePath = filePath;
|
|
||||||
this.contentResolver = contentResolver;
|
|
||||||
try {
|
|
||||||
exifInterface = new ExifInterface(filePath);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Timber.e(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes filePath coordinates, either from EXIF data or user location
|
|
||||||
*/
|
|
||||||
GPSExtractor processFileCoordinates(SimilarImageInterface similarImageInterface, Context context) {
|
|
||||||
// Redact EXIF data as indicated in preferences.
|
|
||||||
redactExifTags(exifInterface, getExifTagsToRedact(context));
|
|
||||||
|
|
||||||
Timber.d("Calling GPSExtractor");
|
|
||||||
imageObj = new GPSExtractor(exifInterface);
|
|
||||||
decimalCoords = imageObj.getCoords();
|
|
||||||
if (decimalCoords == null || !imageObj.imageCoordsExists) {
|
|
||||||
//Find other photos taken around the same time which has gps coordinates
|
|
||||||
if (!haveCheckedForOtherImages)
|
|
||||||
findOtherImages(similarImageInterface);// Do not do repeat the process
|
|
||||||
} else {
|
|
||||||
useImageCoords();
|
|
||||||
}
|
|
||||||
|
|
||||||
return imageObj;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets EXIF Tags from preferences to be redacted.
|
|
||||||
*
|
|
||||||
* @param context application context
|
|
||||||
* @return tags to be redacted
|
|
||||||
*/
|
|
||||||
private Set<String> getExifTagsToRedact(Context context) {
|
|
||||||
Type setType = new TypeToken<Set<String>>() {}.getType();
|
|
||||||
Set<String> prefManageEXIFTags = defaultKvStore.getStringSet(Prefs.MANAGED_EXIF_TAGS);
|
|
||||||
|
|
||||||
Set<String> redactTags = new HashSet<>(Arrays.asList(
|
|
||||||
context.getResources().getStringArray(R.array.pref_exifTag_values)));
|
|
||||||
Timber.d(redactTags.toString());
|
|
||||||
|
|
||||||
if (prefManageEXIFTags != null) redactTags.removeAll(prefManageEXIFTags);
|
|
||||||
|
|
||||||
return redactTags;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redacts EXIF metadata as indicated in preferences.
|
|
||||||
*
|
|
||||||
* @param exifInterface ExifInterface object
|
|
||||||
* @param redactTags tags to be redacted
|
|
||||||
*/
|
|
||||||
public static void redactExifTags(ExifInterface exifInterface, Set<String> redactTags) {
|
|
||||||
if(redactTags.isEmpty()) return;
|
|
||||||
|
|
||||||
Disposable disposable = Observable.fromIterable(redactTags)
|
|
||||||
.flatMap(tag -> Observable.fromArray(FileMetadataUtils.getTagsFromPref(tag)))
|
|
||||||
.forEach(tag -> {
|
|
||||||
Timber.d("Checking for tag: %s", tag);
|
|
||||||
String oldValue = exifInterface.getAttribute(tag);
|
|
||||||
if (oldValue != null && !oldValue.isEmpty()) {
|
|
||||||
Timber.d("Exif tag %s with value %s redacted.", tag, oldValue);
|
|
||||||
exifInterface.setAttribute(tag, null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
CompositeDisposable disposables = new CompositeDisposable();
|
|
||||||
disposables.add(disposable);
|
|
||||||
disposables.clear();
|
|
||||||
|
|
||||||
try {
|
|
||||||
exifInterface.saveAttributes();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Timber.w("EXIF redaction failed: %s", e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find other images around the same location that were taken within the last 20 sec
|
|
||||||
* @param similarImageInterface
|
|
||||||
*/
|
|
||||||
private void findOtherImages(SimilarImageInterface similarImageInterface) {
|
|
||||||
Timber.d("filePath" + filePath);
|
|
||||||
|
|
||||||
long timeOfCreation = new File(filePath).lastModified();//Time when the original image was created
|
|
||||||
File folder = new File(filePath.substring(0, filePath.lastIndexOf('/')));
|
|
||||||
File[] files = folder.listFiles();
|
|
||||||
Timber.d("folderTime Number:" + files.length);
|
|
||||||
|
|
||||||
|
|
||||||
for (File file : files) {
|
|
||||||
if (file.lastModified() - timeOfCreation <= (120 * 1000) && file.lastModified() - timeOfCreation >= -(120 * 1000)) {
|
|
||||||
//Make sure the photos were taken within 20seconds
|
|
||||||
Timber.d("fild date:" + file.lastModified() + " time of creation" + timeOfCreation);
|
|
||||||
tempImageObj = null;//Temporary GPSExtractor to extract coords from these photos
|
|
||||||
try {
|
|
||||||
tempImageObj = new GPSExtractor(contentResolver.openInputStream(Uri.fromFile(file)));
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
if (tempImageObj != null) {
|
|
||||||
tempImageObj = new GPSExtractor(file.getAbsolutePath());
|
|
||||||
}
|
|
||||||
if (tempImageObj != null) {
|
|
||||||
Timber.d("not null fild EXIF" + tempImageObj.imageCoordsExists + " coords" + tempImageObj.getCoords());
|
|
||||||
if (tempImageObj.getCoords() != null && tempImageObj.imageCoordsExists) {
|
|
||||||
// Current image has gps coordinates and it's not current gps locaiton
|
|
||||||
Timber.d("This filePath has image coords:" + file.getAbsolutePath());
|
|
||||||
similarImageInterface.showSimilarImageFragment(filePath, file.getAbsolutePath());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
haveCheckedForOtherImages = true; //Finished checking for other images
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initiates retrieval of image coordinates or user coordinates, and caching of coordinates.
|
|
||||||
* Then initiates the calls to MediaWiki API through an instance of CategoryApi.
|
|
||||||
*/
|
|
||||||
@SuppressLint("CheckResult")
|
|
||||||
private void useImageCoords() {
|
|
||||||
if (decimalCoords != null) {
|
|
||||||
Timber.d("Decimal coords of image: %s", decimalCoords);
|
|
||||||
Timber.d("is EXIF data present:" + imageObj.imageCoordsExists + " from findOther image");
|
|
||||||
|
|
||||||
// Only set cache for this point if image has coords
|
|
||||||
if (imageObj.imageCoordsExists) {
|
|
||||||
double decLongitude = imageObj.getDecLongitude();
|
|
||||||
double decLatitude = imageObj.getDecLatitude();
|
|
||||||
cacheController.setQtPoint(decLongitude, decLatitude);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> displayCatList = cacheController.findCategory();
|
|
||||||
boolean catListEmpty = displayCatList.isEmpty();
|
|
||||||
|
|
||||||
|
|
||||||
// If no categories found in cache, call MediaWiki API to match image coords with nearby Commons categories
|
|
||||||
if (catListEmpty) {
|
|
||||||
compositeDisposable.add(apiCall.request(decimalCoords)
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(Schedulers.io())
|
|
||||||
.subscribe(
|
|
||||||
gpsCategoryModel::setCategoryList,
|
|
||||||
throwable -> {
|
|
||||||
Timber.e(throwable);
|
|
||||||
gpsCategoryModel.clear();
|
|
||||||
}
|
|
||||||
));
|
|
||||||
Timber.d("displayCatList size 0, calling MWAPI %s", displayCatList);
|
|
||||||
} else {
|
|
||||||
Timber.d("Cache found, setting categoryList in model to %s", displayCatList);
|
|
||||||
gpsCategoryModel.setCategoryList(displayCatList);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Timber.d("EXIF: no coords");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPositiveResponse() {
|
|
||||||
imageObj = tempImageObj;
|
|
||||||
decimalCoords = imageObj.getCoords();// Not necessary to use gps as image already ha EXIF data
|
|
||||||
Timber.d("EXIF from tempImageObj");
|
|
||||||
useImageCoords();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNegativeResponse() {
|
|
||||||
Timber.d("EXIF from imageObj");
|
|
||||||
useImageCoords();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
191
app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.kt
Normal file
191
app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.kt
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.exifinterface.media.ExifInterface
|
||||||
|
import fr.free.nrw.commons.R
|
||||||
|
import fr.free.nrw.commons.caching.CacheController
|
||||||
|
import fr.free.nrw.commons.kvstore.JsonKvStore
|
||||||
|
import fr.free.nrw.commons.mwapi.CategoryApi
|
||||||
|
import fr.free.nrw.commons.settings.Prefs
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Named
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processing of the image filePath that is about to be uploaded via ShareActivity is done here
|
||||||
|
*/
|
||||||
|
class FileProcessor @Inject constructor(
|
||||||
|
private val context: Context,
|
||||||
|
private val contentResolver: ContentResolver,
|
||||||
|
private val cacheController: CacheController,
|
||||||
|
private val gpsCategoryModel: GpsCategoryModel,
|
||||||
|
@param:Named("default_preferences") private val defaultKvStore: JsonKvStore,
|
||||||
|
private val apiCall: CategoryApi
|
||||||
|
) {
|
||||||
|
private val compositeDisposable = CompositeDisposable()
|
||||||
|
|
||||||
|
fun cleanup() {
|
||||||
|
compositeDisposable.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes filePath coordinates, either from EXIF data or user location
|
||||||
|
*/
|
||||||
|
fun processFileCoordinates(similarImageInterface: SimilarImageInterface, filePath: String?)
|
||||||
|
: 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)
|
||||||
|
if (originalImageCoordinates.decimalCoords == null) {
|
||||||
|
//Find other photos taken around the same time which has gps coordinates
|
||||||
|
findOtherImages(
|
||||||
|
File(filePath),
|
||||||
|
similarImageInterface
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
useImageCoords(originalImageCoordinates)
|
||||||
|
}
|
||||||
|
return originalImageCoordinates
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets EXIF Tags from preferences to be redacted.
|
||||||
|
*
|
||||||
|
* @return tags to be redacted
|
||||||
|
*/
|
||||||
|
private 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
|
||||||
|
*/
|
||||||
|
private 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 {
|
||||||
|
exifInterface.setAttribute(tag, null).also {
|
||||||
|
Timber.d("Exif tag $tag with value $it 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 {
|
||||||
|
ImageCoordinates(contentResolver.openInputStream(Uri.fromFile(file)))
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Timber.e(e)
|
||||||
|
try {
|
||||||
|
ImageCoordinates(file.absolutePath)
|
||||||
|
} catch (ex: IOException) {
|
||||||
|
Timber.e(ex)
|
||||||
|
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 useImageCoords(imageCoordinates: ImageCoordinates) {
|
||||||
|
requireNotNull(imageCoordinates.decimalCoords)
|
||||||
|
cacheController.setQtPoint(imageCoordinates.decLongitude, imageCoordinates.decLatitude)
|
||||||
|
val displayCatList = cacheController.findCategory()
|
||||||
|
|
||||||
|
// If no categories found in cache, call MediaWiki API to match image coords with nearby Commons categories
|
||||||
|
if (displayCatList.isEmpty()) {
|
||||||
|
compositeDisposable.add(
|
||||||
|
apiCall.request(imageCoordinates.decimalCoords)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(Schedulers.io())
|
||||||
|
.subscribe(
|
||||||
|
{ gpsCategoryModel.categoryList = it },
|
||||||
|
{
|
||||||
|
Timber.e(it)
|
||||||
|
gpsCategoryModel.clear()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Timber.d("displayCatList size 0, calling MWAPI %s", displayCatList)
|
||||||
|
} else {
|
||||||
|
Timber.d("Cache found, setting categoryList in model to %s", displayCatList)
|
||||||
|
gpsCategoryModel.categoryList = displayCatList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -68,9 +68,9 @@ public class FileUtils {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ExifInterface exifInterface = new ExifInterface(filePath);
|
ExifInterface exifInterface = new ExifInterface(filePath);
|
||||||
GPSExtractor imageObj = new GPSExtractor(exifInterface);
|
ImageCoordinates imageObj = new ImageCoordinates(exifInterface);
|
||||||
if (imageObj.imageCoordsExists) { // If image has geolocation information in its EXIF
|
if (imageObj.getDecimalCoords() != null) { // If image has geolocation information in its EXIF
|
||||||
return imageObj.getCoords();
|
return imageObj.getDecimalCoords();
|
||||||
} else {
|
} else {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,152 +0,0 @@
|
||||||
package fr.free.nrw.commons.upload;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.exifinterface.media.ExifInterface;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts geolocation to be passed to API for category suggestions. If a picture with geolocation
|
|
||||||
* is uploaded, extract latitude and longitude from EXIF data of image.
|
|
||||||
*/
|
|
||||||
public class GPSExtractor {
|
|
||||||
|
|
||||||
static final GPSExtractor DUMMY= new GPSExtractor();
|
|
||||||
private double decLatitude;
|
|
||||||
private double decLongitude;
|
|
||||||
public boolean imageCoordsExists;
|
|
||||||
private String latitude;
|
|
||||||
private String longitude;
|
|
||||||
private String latitudeRef;
|
|
||||||
private String longitudeRef;
|
|
||||||
private String decimalCoords;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dummy constructor.
|
|
||||||
*/
|
|
||||||
private GPSExtractor(){
|
|
||||||
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Construct from a stream.
|
|
||||||
*/
|
|
||||||
GPSExtractor(@NonNull InputStream stream) throws IOException {
|
|
||||||
ExifInterface exif = new ExifInterface(stream);
|
|
||||||
processCoords(exif);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct from the file path of the image.
|
|
||||||
* @param path file path of the image
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
GPSExtractor(@NonNull String path) {
|
|
||||||
try {
|
|
||||||
ExifInterface exif = new ExifInterface(path);
|
|
||||||
processCoords(exif);
|
|
||||||
} catch (IOException | IllegalArgumentException e) {
|
|
||||||
Timber.w(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct from the file path of the image.
|
|
||||||
* @param exif exif interface of the image
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
GPSExtractor(@NonNull ExifInterface exif){
|
|
||||||
processCoords(exif);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processCoords(ExifInterface exif){
|
|
||||||
//If image has no EXIF data and user has enabled GPS setting, get user's location
|
|
||||||
//Always return null as a temporary fix for #1599
|
|
||||||
if (exif != null && exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) != null) {
|
|
||||||
//If image has EXIF data, extract image coords
|
|
||||||
imageCoordsExists = true;
|
|
||||||
Timber.d("EXIF data has location info");
|
|
||||||
|
|
||||||
latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
|
|
||||||
latitudeRef = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
|
|
||||||
longitude = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE);
|
|
||||||
longitudeRef = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts geolocation (either of image from EXIF data, or of user)
|
|
||||||
* @return coordinates as string (needs to be passed as a String in API query)
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
String getCoords() {
|
|
||||||
if(decimalCoords!=null){
|
|
||||||
return decimalCoords;
|
|
||||||
}else if (latitude!=null && latitudeRef!=null && longitude!=null && longitudeRef!=null) {
|
|
||||||
Timber.d("Latitude: %s %s", latitude, latitudeRef);
|
|
||||||
Timber.d("Longitude: %s %s", longitude, longitudeRef);
|
|
||||||
|
|
||||||
decimalCoords = getDecimalCoords(latitude, latitudeRef, longitude, longitudeRef);
|
|
||||||
return decimalCoords;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getDecLatitude() {
|
|
||||||
return decLatitude;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getDecLongitude() {
|
|
||||||
return decLongitude;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts format of geolocation into decimal coordinates as required by MediaWiki API
|
|
||||||
* @return the coordinates in decimals
|
|
||||||
*/
|
|
||||||
private String getDecimalCoords(String latitude, String latitude_ref, String longitude, String longitude_ref) {
|
|
||||||
|
|
||||||
if (latitude_ref.equals("N")) {
|
|
||||||
decLatitude = convertToDegree(latitude);
|
|
||||||
} else {
|
|
||||||
decLatitude = 0 - convertToDegree(latitude);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (longitude_ref.equals("E")) {
|
|
||||||
decLongitude = convertToDegree(longitude);
|
|
||||||
} else {
|
|
||||||
decLongitude = 0 - convertToDegree(longitude);
|
|
||||||
}
|
|
||||||
|
|
||||||
String decimalCoords = decLatitude + "|" + decLongitude;
|
|
||||||
Timber.d("Latitude and Longitude are %s", decimalCoords);
|
|
||||||
return decimalCoords;
|
|
||||||
}
|
|
||||||
|
|
||||||
private double convertToDegree(String stringDMS) {
|
|
||||||
double result;
|
|
||||||
String[] DMS = stringDMS.split(",", 3);
|
|
||||||
|
|
||||||
String[] stringD = DMS[0].split("/", 2);
|
|
||||||
double d0 = Double.parseDouble(stringD[0]);
|
|
||||||
double d1 = Double.parseDouble(stringD[1]);
|
|
||||||
double degrees = d0/d1;
|
|
||||||
|
|
||||||
String[] stringM = DMS[1].split("/", 2);
|
|
||||||
double m0 = Double.parseDouble(stringM[0]);
|
|
||||||
double m1 = Double.parseDouble(stringM[1]);
|
|
||||||
double minutes = m0/m1;
|
|
||||||
|
|
||||||
String[] stringS = DMS[2].split("/", 2);
|
|
||||||
double s0 = Double.parseDouble(stringS[0]);
|
|
||||||
double s1 = Double.parseDouble(stringS[1]);
|
|
||||||
double seconds = s0/s1;
|
|
||||||
|
|
||||||
result = degrees + (minutes/60) + (seconds/3600);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
|
import androidx.exifinterface.media.ExifInterface
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts geolocation to be passed to API for category suggestions. If a picture with geolocation
|
||||||
|
* is uploaded, extract latitude and longitude from EXIF data of image.
|
||||||
|
*/
|
||||||
|
class ImageCoordinates internal constructor(exif: ExifInterface?) {
|
||||||
|
var decLatitude = 0.0
|
||||||
|
var decLongitude = 0.0
|
||||||
|
var imageCoordsExists = false
|
||||||
|
/**
|
||||||
|
* @return string of `"[decLatitude]|[decLongitude]"` or null if coordinates do not exist
|
||||||
|
*/
|
||||||
|
var decimalCoords: String? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct from a stream.
|
||||||
|
*/
|
||||||
|
internal constructor(stream: InputStream) : this(ExifInterface(stream))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct from the file path of the image.
|
||||||
|
* @param path file path of the image
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
internal constructor(path: String) : this(ExifInterface(path))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
init {
|
||||||
|
//If image has no EXIF data and user has enabled GPS setting, get user's location
|
||||||
|
//Always return null as a temporary fix for #1599
|
||||||
|
if (exif != null) {
|
||||||
|
val latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE)
|
||||||
|
val latitudeRef = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF)
|
||||||
|
val longitude = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE)
|
||||||
|
val longitudeRef = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF)
|
||||||
|
if (latitude != null && longitude != null && latitudeRef != null && longitudeRef != null) {
|
||||||
|
//If image has EXIF data, extract image coords
|
||||||
|
imageCoordsExists = true
|
||||||
|
Timber.d("EXIF data has location info")
|
||||||
|
decLatitude =
|
||||||
|
if (ExifInterface.LATITUDE_NORTH == latitudeRef) convertToDegree(latitude)
|
||||||
|
else 0 - convertToDegree(latitude)
|
||||||
|
decLongitude =
|
||||||
|
if (ExifInterface.LONGITUDE_EAST == longitudeRef) convertToDegree(longitude)
|
||||||
|
else 0 - convertToDegree(longitude)
|
||||||
|
decimalCoords = "$decLatitude|$decLongitude"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a string to an accurate Degree
|
||||||
|
*
|
||||||
|
* @param degreeMinuteSecondString - template string "a/b,c/d,e/f" where the letters represent numbers
|
||||||
|
* @return the degree accurate to the second
|
||||||
|
*/
|
||||||
|
private fun convertToDegree(degreeMinuteSecondString: String) =
|
||||||
|
degreeMinuteSecondString.split(",").let {
|
||||||
|
val degrees = evaluateExpression(it[0])
|
||||||
|
val minutes = evaluateExpression(it[1])
|
||||||
|
val seconds = evaluateExpression(it[2])
|
||||||
|
degrees + minutes / 60 + seconds / 3600
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun evaluateExpression(dm: String) =
|
||||||
|
dm.split("/").let { it[0].toDouble() / it[1].toDouble() }
|
||||||
|
}
|
||||||
|
|
@ -9,20 +9,16 @@ import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
|
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
|
||||||
|
|
||||||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
|
||||||
import com.facebook.drawee.view.SimpleDraweeView;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import butterknife.OnClick;
|
import butterknife.OnClick;
|
||||||
|
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||||
|
import com.facebook.drawee.view.SimpleDraweeView;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by harisanker on 14/2/18.
|
* Created by harisanker on 14/2/18.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package fr.free.nrw.commons.upload;
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
public interface SimilarImageInterface {
|
public interface SimilarImageInterface {
|
||||||
void showSimilarImageFragment(String originalFilePath, String possibleFilePath);
|
void showSimilarImageFragment(String originalFilePath, String possibleFilePath,
|
||||||
|
ImageCoordinates similarImageCoordinates);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,7 @@ package fr.free.nrw.commons.upload;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.Utils;
|
import fr.free.nrw.commons.Utils;
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
|
|
@ -31,26 +18,25 @@ import io.reactivex.Observable;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
import io.reactivex.subjects.BehaviorSubject;
|
import io.reactivex.subjects.BehaviorSubject;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class UploadModel {
|
public class UploadModel {
|
||||||
|
|
||||||
private static UploadItem DUMMY = new UploadItem(
|
|
||||||
Uri.EMPTY, Uri.EMPTY,
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
GPSExtractor.DUMMY,
|
|
||||||
null,
|
|
||||||
-1L, "") {
|
|
||||||
};
|
|
||||||
private final JsonKvStore store;
|
private final JsonKvStore store;
|
||||||
private final List<String> licenses;
|
private final List<String> licenses;
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private String license;
|
private String license;
|
||||||
private final Map<String, String> licensesByName;
|
private final Map<String, String> licensesByName;
|
||||||
private List<UploadItem> items = new ArrayList<>();
|
private List<UploadItem> items = new ArrayList<>();
|
||||||
private int currentStepIndex = 0;
|
|
||||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||||
|
|
||||||
private SessionManager sessionManager;
|
private SessionManager sessionManager;
|
||||||
|
|
@ -95,19 +81,6 @@ public class UploadModel {
|
||||||
this.selectedCategories = selectedCategories;
|
this.selectedCategories = selectedCategories;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* pre process a list of items
|
|
||||||
*/
|
|
||||||
@SuppressLint("CheckResult")
|
|
||||||
Observable<UploadItem> preProcessImages(List<UploadableFile> uploadableFiles,
|
|
||||||
Place place,
|
|
||||||
String source,
|
|
||||||
SimilarImageInterface similarImageInterface) {
|
|
||||||
return Observable.fromIterable(uploadableFiles)
|
|
||||||
.map(uploadableFile -> getUploadItem(uploadableFile, place, source,
|
|
||||||
similarImageInterface));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pre process a one item at a time
|
* pre process a one item at a time
|
||||||
|
|
@ -127,8 +100,6 @@ public class UploadModel {
|
||||||
Place place,
|
Place place,
|
||||||
String source,
|
String source,
|
||||||
SimilarImageInterface similarImageInterface) {
|
SimilarImageInterface similarImageInterface) {
|
||||||
fileProcessor.initFileDetails(Objects.requireNonNull(uploadableFile.getFilePath()),
|
|
||||||
context.getContentResolver());
|
|
||||||
UploadableFile.DateTimeWithSource dateTimeWithSource = uploadableFile
|
UploadableFile.DateTimeWithSource dateTimeWithSource = uploadableFile
|
||||||
.getFileCreatedDate(context);
|
.getFileCreatedDate(context);
|
||||||
long fileCreatedDate = -1;
|
long fileCreatedDate = -1;
|
||||||
|
|
@ -138,11 +109,11 @@ public class UploadModel {
|
||||||
createdTimestampSource = dateTimeWithSource.getSource();
|
createdTimestampSource = dateTimeWithSource.getSource();
|
||||||
}
|
}
|
||||||
Timber.d("File created date is %d", fileCreatedDate);
|
Timber.d("File created date is %d", fileCreatedDate);
|
||||||
GPSExtractor gpsExtractor = fileProcessor
|
ImageCoordinates imageCoordinates = fileProcessor
|
||||||
.processFileCoordinates(similarImageInterface, context);
|
.processFileCoordinates(similarImageInterface, uploadableFile.getFilePath());
|
||||||
UploadItem uploadItem = new UploadItem(uploadableFile.getContentUri(),
|
UploadItem uploadItem = new UploadItem(uploadableFile.getContentUri(),
|
||||||
Uri.parse(uploadableFile.getFilePath()),
|
Uri.parse(uploadableFile.getFilePath()),
|
||||||
uploadableFile.getMimeType(context), source, gpsExtractor, place, fileCreatedDate,
|
uploadableFile.getMimeType(context), source, imageCoordinates, place, fileCreatedDate,
|
||||||
createdTimestampSource);
|
createdTimestampSource);
|
||||||
if (place != null) {
|
if (place != null) {
|
||||||
uploadItem.title.setTitleText(place.name);
|
uploadItem.title.setTitleText(place.name);
|
||||||
|
|
@ -158,14 +129,6 @@ public class UploadModel {
|
||||||
return uploadItem;
|
return uploadItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getCurrentStep() {
|
|
||||||
return currentStepIndex + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getStepCount() {
|
|
||||||
return items.size() + 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCount() {
|
public int getCount() {
|
||||||
return items.size();
|
return items.size();
|
||||||
}
|
}
|
||||||
|
|
@ -194,7 +157,7 @@ public class UploadModel {
|
||||||
item.getFileName(),
|
item.getFileName(),
|
||||||
Description.formatList(item.descriptions), -1,
|
Description.formatList(item.descriptions), -1,
|
||||||
null, null, sessionManager.getAuthorName(),
|
null, null, sessionManager.getAuthorName(),
|
||||||
CommonsApplication.DEFAULT_EDIT_SUMMARY, item.gpsCoords.getCoords());
|
CommonsApplication.DEFAULT_EDIT_SUMMARY, item.gpsCoords.getDecimalCoords());
|
||||||
if (item.place != null) {
|
if (item.place != null) {
|
||||||
contribution.setWikiDataEntityId(item.place.getWikiDataEntityId());
|
contribution.setWikiDataEntityId(item.place.getWikiDataEntityId());
|
||||||
// If item already has an image, we need to know it. We don't want to override existing image later
|
// If item already has an image, we need to know it. We don't want to override existing image later
|
||||||
|
|
@ -244,6 +207,11 @@ public class UploadModel {
|
||||||
uploadItem1.setTitle(uploadItem.title);
|
uploadItem1.setTitle(uploadItem.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex) {
|
||||||
|
fileProcessor.useImageCoords(imageCoordinates);
|
||||||
|
items.get(uploadItemIndex).setGpsCoords(imageCoordinates);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
public static class UploadItem {
|
public static class UploadItem {
|
||||||
|
|
||||||
|
|
@ -251,22 +219,22 @@ public class UploadModel {
|
||||||
private final Uri mediaUri;
|
private final Uri mediaUri;
|
||||||
private final String mimeType;
|
private final String mimeType;
|
||||||
private final String source;
|
private final String source;
|
||||||
private final GPSExtractor gpsCoords;
|
private ImageCoordinates gpsCoords;
|
||||||
|
|
||||||
|
public void setGpsCoords(ImageCoordinates gpsCoords) {
|
||||||
|
this.gpsCoords = gpsCoords;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean selected = false;
|
|
||||||
private boolean first = false;
|
|
||||||
private Title title;
|
private Title title;
|
||||||
private List<Description> descriptions;
|
private List<Description> descriptions;
|
||||||
private Place place;
|
private Place place;
|
||||||
private boolean visited;
|
|
||||||
private boolean error;
|
|
||||||
private long createdTimestamp;
|
private long createdTimestamp;
|
||||||
private String createdTimestampSource;
|
private String createdTimestampSource;
|
||||||
private BehaviorSubject<Integer> imageQuality;
|
private BehaviorSubject<Integer> imageQuality;
|
||||||
|
|
||||||
@SuppressLint("CheckResult")
|
@SuppressLint("CheckResult")
|
||||||
UploadItem(Uri originalContentUri,
|
UploadItem(Uri originalContentUri,
|
||||||
Uri mediaUri, String mimeType, String source, GPSExtractor gpsCoords,
|
Uri mediaUri, String mimeType, String source, ImageCoordinates gpsCoords,
|
||||||
Place place,
|
Place place,
|
||||||
long createdTimestamp,
|
long createdTimestamp,
|
||||||
String createdTimestampSource) {
|
String createdTimestampSource) {
|
||||||
|
|
@ -287,38 +255,18 @@ public class UploadModel {
|
||||||
return createdTimestampSource;
|
return createdTimestampSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMimeType() {
|
|
||||||
return mimeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSource() {
|
public String getSource() {
|
||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GPSExtractor getGpsCoords() {
|
public ImageCoordinates getGpsCoords() {
|
||||||
return gpsCoords;
|
return gpsCoords;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSelected() {
|
|
||||||
return selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isFirst() {
|
|
||||||
return first;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Description> getDescriptions() {
|
public List<Description> getDescriptions() {
|
||||||
return descriptions;
|
return descriptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isVisited() {
|
|
||||||
return visited;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isError() {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getCreatedTimestamp() {
|
public long getCreatedTimestamp() {
|
||||||
return createdTimestamp;
|
return createdTimestamp;
|
||||||
}
|
}
|
||||||
|
|
@ -373,10 +321,9 @@ public class UploadModel {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Travis is complaining :P
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return super.hashCode();
|
return mediaUri.hashCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package fr.free.nrw.commons.upload.mediaDetails;
|
package fr.free.nrw.commons.upload.mediaDetails;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.utils.ImageUtils.getErrorMessageForResult;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
@ -12,30 +14,17 @@ import android.view.ViewGroup;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.widget.AppCompatButton;
|
import androidx.appcompat.widget.AppCompatButton;
|
||||||
import androidx.appcompat.widget.AppCompatImageButton;
|
import androidx.appcompat.widget.AppCompatImageButton;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.github.chrisbanes.photoview.PhotoView;
|
|
||||||
import com.jakewharton.rxbinding2.widget.RxTextView;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import butterknife.OnClick;
|
import butterknife.OnClick;
|
||||||
|
import com.github.chrisbanes.photoview.PhotoView;
|
||||||
|
import com.jakewharton.rxbinding2.widget.RxTextView;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.Utils;
|
import fr.free.nrw.commons.Utils;
|
||||||
import fr.free.nrw.commons.filepicker.UploadableFile;
|
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
|
|
@ -45,6 +34,7 @@ import fr.free.nrw.commons.nearby.Place;
|
||||||
import fr.free.nrw.commons.settings.Prefs;
|
import fr.free.nrw.commons.settings.Prefs;
|
||||||
import fr.free.nrw.commons.upload.Description;
|
import fr.free.nrw.commons.upload.Description;
|
||||||
import fr.free.nrw.commons.upload.DescriptionsAdapter;
|
import fr.free.nrw.commons.upload.DescriptionsAdapter;
|
||||||
|
import fr.free.nrw.commons.upload.ImageCoordinates;
|
||||||
import fr.free.nrw.commons.upload.SimilarImageDialogFragment;
|
import fr.free.nrw.commons.upload.SimilarImageDialogFragment;
|
||||||
import fr.free.nrw.commons.upload.Title;
|
import fr.free.nrw.commons.upload.Title;
|
||||||
import fr.free.nrw.commons.upload.UploadBaseFragment;
|
import fr.free.nrw.commons.upload.UploadBaseFragment;
|
||||||
|
|
@ -54,10 +44,15 @@ import fr.free.nrw.commons.utils.DialogUtil;
|
||||||
import fr.free.nrw.commons.utils.ImageUtils;
|
import fr.free.nrw.commons.utils.ImageUtils;
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static fr.free.nrw.commons.utils.ImageUtils.getErrorMessageForResult;
|
|
||||||
|
|
||||||
public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
UploadMediaDetailsContract.View {
|
UploadMediaDetailsContract.View {
|
||||||
|
|
||||||
|
|
@ -231,14 +226,6 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
rvDescriptions.setAdapter(descriptionsAdapter);
|
rvDescriptions.setAdapter(descriptionsAdapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* returns the default locale value of the user's device
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private String getUserDefaultLocale() {
|
|
||||||
return getContext().getResources().getConfiguration().locale.getLanguage();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* show dialog with info
|
* show dialog with info
|
||||||
* @param titleStringID
|
* @param titleStringID
|
||||||
|
|
@ -267,12 +254,14 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showSimilarImageFragment(String originalFilePath, String possibleFilePath) {
|
public void showSimilarImageFragment(String originalFilePath, String possibleFilePath,
|
||||||
|
ImageCoordinates similarImageCoordinates) {
|
||||||
SimilarImageDialogFragment newFragment = new SimilarImageDialogFragment();
|
SimilarImageDialogFragment newFragment = new SimilarImageDialogFragment();
|
||||||
newFragment.setCallback(new SimilarImageDialogFragment.Callback() {
|
newFragment.setCallback(new SimilarImageDialogFragment.Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void onPositiveResponse() {
|
public void onPositiveResponse() {
|
||||||
Timber.d("positive response from similar image fragment");
|
Timber.d("positive response from similar image fragment");
|
||||||
|
presenter.useSimilarPictureCoordinates(similarImageCoordinates, callback.getIndexInViewFlipper(UploadMediaDetailFragment.this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package fr.free.nrw.commons.upload.mediaDetails;
|
package fr.free.nrw.commons.upload.mediaDetails;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.upload.ImageCoordinates;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import fr.free.nrw.commons.BasePresenter;
|
import fr.free.nrw.commons.BasePresenter;
|
||||||
|
|
@ -48,6 +49,9 @@ public interface UploadMediaDetailsContract {
|
||||||
void setUploadItem(int index, UploadItem uploadItem);
|
void setUploadItem(int index, UploadItem uploadItem);
|
||||||
|
|
||||||
void fetchPreviousTitleAndDescription(int indexInViewFlipper);
|
void fetchPreviousTitleAndDescription(int indexInViewFlipper);
|
||||||
|
|
||||||
|
void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
package fr.free.nrw.commons.upload.mediaDetails;
|
package fr.free.nrw.commons.upload.mediaDetails;
|
||||||
|
|
||||||
import java.lang.reflect.Proxy;
|
import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD;
|
||||||
|
import static fr.free.nrw.commons.di.CommonsApplicationModule.MAIN_THREAD;
|
||||||
import javax.inject.Inject;
|
import static fr.free.nrw.commons.utils.ImageUtils.EMPTY_TITLE;
|
||||||
import javax.inject.Named;
|
import static fr.free.nrw.commons.utils.ImageUtils.FILE_NAME_EXISTS;
|
||||||
|
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_KEEP;
|
||||||
|
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK;
|
||||||
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.filepicker.UploadableFile;
|
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
import fr.free.nrw.commons.repository.UploadRepository;
|
import fr.free.nrw.commons.repository.UploadRepository;
|
||||||
import fr.free.nrw.commons.upload.GPSExtractor;
|
import fr.free.nrw.commons.upload.ImageCoordinates;
|
||||||
import fr.free.nrw.commons.upload.SimilarImageInterface;
|
import fr.free.nrw.commons.upload.SimilarImageInterface;
|
||||||
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
|
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
|
||||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailsContract.UserActionListener;
|
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailsContract.UserActionListener;
|
||||||
|
|
@ -18,15 +20,11 @@ import io.reactivex.Observable;
|
||||||
import io.reactivex.Scheduler;
|
import io.reactivex.Scheduler;
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD;
|
|
||||||
import static fr.free.nrw.commons.di.CommonsApplicationModule.MAIN_THREAD;
|
|
||||||
import static fr.free.nrw.commons.utils.ImageUtils.EMPTY_TITLE;
|
|
||||||
import static fr.free.nrw.commons.utils.ImageUtils.FILE_NAME_EXISTS;
|
|
||||||
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_KEEP;
|
|
||||||
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK;
|
|
||||||
|
|
||||||
public class UploadMediaPresenter implements UserActionListener, SimilarImageInterface {
|
public class UploadMediaPresenter implements UserActionListener, SimilarImageInterface {
|
||||||
|
|
||||||
private static final UploadMediaDetailsContract.View DUMMY = (UploadMediaDetailsContract.View) Proxy
|
private static final UploadMediaDetailsContract.View DUMMY = (UploadMediaDetailsContract.View) Proxy
|
||||||
|
|
@ -81,10 +79,10 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
|
||||||
.subscribe(uploadItem ->
|
.subscribe(uploadItem ->
|
||||||
{
|
{
|
||||||
view.onImageProcessed(uploadItem, place);
|
view.onImageProcessed(uploadItem, place);
|
||||||
GPSExtractor gpsCoords = uploadItem.getGpsCoords();
|
ImageCoordinates gpsCoords = uploadItem.getGpsCoords();
|
||||||
view.showMapWithImageCoordinates(gpsCoords != null && gpsCoords.imageCoordsExists);
|
view.showMapWithImageCoordinates(gpsCoords != null && gpsCoords.getImageCoordsExists());
|
||||||
view.showProgress(false);
|
view.showProgress(false);
|
||||||
if (gpsCoords != null && gpsCoords.imageCoordsExists) {
|
if (gpsCoords != null && gpsCoords.getImageCoordsExists()) {
|
||||||
checkNearbyPlaces(uploadItem);
|
checkNearbyPlaces(uploadItem);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -159,6 +157,11 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex) {
|
||||||
|
repository.useSimilarPictureCoordinates(imageCoordinates, uploadItemIndex);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* handles image quality verifications
|
* handles image quality verifications
|
||||||
*
|
*
|
||||||
|
|
@ -200,12 +203,15 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* notifies the user that a similar image exists
|
* notifies the user that a similar image exists
|
||||||
*
|
|
||||||
* @param originalFilePath
|
* @param originalFilePath
|
||||||
* @param possibleFilePath
|
* @param possibleFilePath
|
||||||
|
* @param similarImageCoordinates
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void showSimilarImageFragment(String originalFilePath, String possibleFilePath) {
|
public void showSimilarImageFragment(String originalFilePath, String possibleFilePath,
|
||||||
view.showSimilarImageFragment(originalFilePath, possibleFilePath);
|
ImageCoordinates similarImageCoordinates) {
|
||||||
|
view.showSimilarImageFragment(originalFilePath, possibleFilePath,
|
||||||
|
similarImageCoordinates
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
package fr.free.nrw.commons.upload
|
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import androidx.exifinterface.media.ExifInterface
|
|
||||||
import fr.free.nrw.commons.caching.CacheController
|
|
||||||
import fr.free.nrw.commons.mwapi.CategoryApi
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
import org.mockito.InjectMocks
|
|
||||||
import org.mockito.Mock
|
|
||||||
import org.mockito.MockitoAnnotations
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Named
|
|
||||||
|
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
|
|
||||||
class FileProcessorTest {
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
internal var cacheController: CacheController? = null
|
|
||||||
@Mock
|
|
||||||
internal var gpsCategoryModel: GpsCategoryModel? = null
|
|
||||||
@Mock
|
|
||||||
internal var apiCall: CategoryApi? = null
|
|
||||||
@Mock
|
|
||||||
@field:[Inject Named("default_preferences")]
|
|
||||||
internal var prefs: SharedPreferences? = null
|
|
||||||
|
|
||||||
@InjectMocks
|
|
||||||
var fileProcessor: FileProcessor? = null
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
MockitoAnnotations.initMocks(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun processFileCoordinates() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test method to verify redaction Exif metadata
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
fun redactExifTags() {
|
|
||||||
/*
|
|
||||||
val filePathRef: String? = "src/test/data/exif_redact_sample.jpg"
|
|
||||||
val filePathTmp: String? = "" + System.getProperty("java.io.tmpdir") + "exif_redact_sample_tmp.jpg"
|
|
||||||
|
|
||||||
val inStream = FileInputStream(filePathRef)
|
|
||||||
val outStream = FileOutputStream(filePathTmp)
|
|
||||||
val inChannel = inStream.getChannel()
|
|
||||||
val outChannel = outStream.getChannel()
|
|
||||||
inChannel.transferTo(0, inChannel.size(), outChannel)
|
|
||||||
inStream.close()
|
|
||||||
outStream.close()
|
|
||||||
|
|
||||||
val redactTags = mutableSetOf("Author", "Copyright", "Location", "Camera Model",
|
|
||||||
"Lens Model", "Serial Numbers", "Software")
|
|
||||||
|
|
||||||
val exifInterface : ExifInterface? = ExifInterface(filePathTmp.toString())
|
|
||||||
|
|
||||||
var nonEmptyTag = false
|
|
||||||
for (redactTag in redactTags) {
|
|
||||||
for (tag in FileMetadataUtils.getTagsFromPref(redactTag)) {
|
|
||||||
val tagValue = exifInterface?.getAttribute(tag)
|
|
||||||
if(tagValue != null) {
|
|
||||||
nonEmptyTag = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (nonEmptyTag) break
|
|
||||||
}
|
|
||||||
// all tags are empty, can't test redaction
|
|
||||||
assert(nonEmptyTag)
|
|
||||||
|
|
||||||
FileProcessor.redactExifTags(exifInterface, redactTags)
|
|
||||||
|
|
||||||
for (redactTag in redactTags) {
|
|
||||||
for (tag in FileMetadataUtils.getTagsFromPref(redactTag)) {
|
|
||||||
val oldValue = exifInterface?.getAttribute(tag)
|
|
||||||
assert(oldValue == null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package fr.free.nrw.commons.upload
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
|
import com.nhaarman.mockitokotlin2.mock
|
||||||
|
import com.nhaarman.mockitokotlin2.whenever
|
||||||
import fr.free.nrw.commons.filepicker.UploadableFile
|
import fr.free.nrw.commons.filepicker.UploadableFile
|
||||||
import fr.free.nrw.commons.nearby.Place
|
import fr.free.nrw.commons.nearby.Place
|
||||||
import fr.free.nrw.commons.repository.UploadRepository
|
import fr.free.nrw.commons.repository.UploadRepository
|
||||||
|
|
@ -14,7 +16,6 @@ import org.junit.Test
|
||||||
import org.mockito.ArgumentMatchers
|
import org.mockito.ArgumentMatchers
|
||||||
import org.mockito.ArgumentMatchers.eq
|
import org.mockito.ArgumentMatchers.eq
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito
|
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
|
@ -24,31 +25,32 @@ import org.mockito.MockitoAnnotations
|
||||||
*/
|
*/
|
||||||
class UploadMediaPresenterTest {
|
class UploadMediaPresenterTest {
|
||||||
@Mock
|
@Mock
|
||||||
internal var repository: UploadRepository? = null
|
internal lateinit var repository: UploadRepository
|
||||||
@Mock
|
|
||||||
internal var view: UploadMediaDetailsContract.View? = null
|
|
||||||
|
|
||||||
private var uploadMediaPresenter: UploadMediaPresenter? = null
|
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private var uploadableFile: UploadableFile? = null
|
internal lateinit var view: UploadMediaDetailsContract.View
|
||||||
|
|
||||||
|
private lateinit var uploadMediaPresenter: UploadMediaPresenter
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private var place: Place? = null
|
private lateinit var uploadableFile: UploadableFile
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private var uploadItem: UploadModel.UploadItem? = null
|
private lateinit var place: Place
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private var title: Title? = null
|
private lateinit var uploadItem: UploadModel.UploadItem
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private var descriptions: List<Description>? = null
|
private lateinit var title: Title
|
||||||
|
|
||||||
private var testObservableUploadItem: Observable<UploadModel.UploadItem>? = null
|
@Mock
|
||||||
private var testSingleImageResult: Single<Int>? = null
|
private lateinit var descriptions: List<Description>
|
||||||
|
|
||||||
private var testScheduler: TestScheduler? = null
|
private lateinit var testObservableUploadItem: Observable<UploadModel.UploadItem>
|
||||||
|
private lateinit var testSingleImageResult: Single<Int>
|
||||||
|
|
||||||
|
private lateinit var testScheduler: TestScheduler
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* initial setup unit test environment
|
* initial setup unit test environment
|
||||||
|
|
@ -61,7 +63,7 @@ class UploadMediaPresenterTest {
|
||||||
testSingleImageResult = Single.just(1)
|
testSingleImageResult = Single.just(1)
|
||||||
testScheduler = TestScheduler()
|
testScheduler = TestScheduler()
|
||||||
uploadMediaPresenter = UploadMediaPresenter(repository, testScheduler, testScheduler)
|
uploadMediaPresenter = UploadMediaPresenter(repository, testScheduler, testScheduler)
|
||||||
uploadMediaPresenter?.onAttachView(view)
|
uploadMediaPresenter.onAttachView(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -69,12 +71,22 @@ class UploadMediaPresenterTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun receiveImageTest() {
|
fun receiveImageTest() {
|
||||||
Mockito.`when`(repository?.preProcessImage(ArgumentMatchers.any(UploadableFile::class.java), ArgumentMatchers.any(Place::class.java), ArgumentMatchers.anyString(), ArgumentMatchers.any(UploadMediaPresenter::class.java))).thenReturn(testObservableUploadItem)
|
whenever(
|
||||||
uploadMediaPresenter?.receiveImage(uploadableFile, ArgumentMatchers.anyString(), place)
|
repository.preProcessImage(
|
||||||
verify(view)?.showProgress(true)
|
ArgumentMatchers.any(UploadableFile::class.java),
|
||||||
testScheduler?.triggerActions()
|
ArgumentMatchers.any(Place::class.java),
|
||||||
verify(view)?.onImageProcessed(ArgumentMatchers.any(UploadModel.UploadItem::class.java), ArgumentMatchers.any(Place::class.java))
|
ArgumentMatchers.anyString(),
|
||||||
verify(view)?.showProgress(false)
|
ArgumentMatchers.any(UploadMediaPresenter::class.java)
|
||||||
|
)
|
||||||
|
).thenReturn(testObservableUploadItem)
|
||||||
|
uploadMediaPresenter.receiveImage(uploadableFile, ArgumentMatchers.anyString(), place)
|
||||||
|
verify(view).showProgress(true)
|
||||||
|
testScheduler.triggerActions()
|
||||||
|
verify(view).onImageProcessed(
|
||||||
|
ArgumentMatchers.any(UploadModel.UploadItem::class.java),
|
||||||
|
ArgumentMatchers.any(Place::class.java)
|
||||||
|
)
|
||||||
|
verify(view).showProgress(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -82,12 +94,13 @@ class UploadMediaPresenterTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun verifyImageQualityTest() {
|
fun verifyImageQualityTest() {
|
||||||
Mockito.`when`(repository?.getImageQuality(ArgumentMatchers.any(UploadModel.UploadItem::class.java))).thenReturn(testSingleImageResult)
|
whenever(repository.getImageQuality(ArgumentMatchers.any(UploadModel.UploadItem::class.java)))
|
||||||
Mockito.`when`(uploadItem?.imageQuality).thenReturn(ArgumentMatchers.anyInt())
|
.thenReturn(testSingleImageResult)
|
||||||
uploadMediaPresenter?.verifyImageQuality(uploadItem)
|
whenever(uploadItem.imageQuality).thenReturn(ArgumentMatchers.anyInt())
|
||||||
verify(view)?.showProgress(true)
|
uploadMediaPresenter.verifyImageQuality(uploadItem)
|
||||||
testScheduler?.triggerActions()
|
verify(view).showProgress(true)
|
||||||
verify(view)?.showProgress(false)
|
testScheduler.triggerActions()
|
||||||
|
verify(view).showProgress(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -96,21 +109,21 @@ class UploadMediaPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun handleImageResult() {
|
fun handleImageResult() {
|
||||||
//Positive case test
|
//Positive case test
|
||||||
uploadMediaPresenter?.handleImageResult(IMAGE_KEEP)
|
uploadMediaPresenter.handleImageResult(IMAGE_KEEP)
|
||||||
verify(view)?.onImageValidationSuccess()
|
verify(view).onImageValidationSuccess()
|
||||||
|
|
||||||
//Duplicate file name
|
//Duplicate file name
|
||||||
uploadMediaPresenter?.handleImageResult(FILE_NAME_EXISTS)
|
uploadMediaPresenter.handleImageResult(FILE_NAME_EXISTS)
|
||||||
verify(view)?.showDuplicatePicturePopup()
|
verify(view).showDuplicatePicturePopup()
|
||||||
|
|
||||||
//Empty Title test
|
//Empty Title test
|
||||||
uploadMediaPresenter?.handleImageResult(EMPTY_TITLE)
|
uploadMediaPresenter.handleImageResult(EMPTY_TITLE)
|
||||||
verify(view)?.showMessage(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())
|
verify(view).showMessage(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())
|
||||||
|
|
||||||
//Bad Picture test
|
//Bad Picture test
|
||||||
//Empty Title test
|
//Empty Title test
|
||||||
uploadMediaPresenter?.handleImageResult(-7)
|
uploadMediaPresenter.handleImageResult(-7)
|
||||||
verify(view)?.showBadImagePopup(ArgumentMatchers.anyInt())
|
verify(view).showBadImagePopup(ArgumentMatchers.anyInt())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,13 +132,14 @@ class UploadMediaPresenterTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun fetchPreviousImageAndTitleTestPositive() {
|
fun fetchPreviousImageAndTitleTestPositive() {
|
||||||
Mockito.`when`(repository?.getPreviousUploadItem(ArgumentMatchers.anyInt())).thenReturn(uploadItem)
|
whenever(repository.getPreviousUploadItem(ArgumentMatchers.anyInt()))
|
||||||
Mockito.`when`(uploadItem?.descriptions).thenReturn(descriptions)
|
.thenReturn(uploadItem)
|
||||||
Mockito.`when`(uploadItem?.title).thenReturn(title)
|
whenever(uploadItem.descriptions).thenReturn(descriptions)
|
||||||
Mockito.`when`(title?.getTitleText()).thenReturn(ArgumentMatchers.anyString())
|
whenever(uploadItem.title).thenReturn(title)
|
||||||
|
whenever(title.getTitleText()).thenReturn(ArgumentMatchers.anyString())
|
||||||
|
|
||||||
uploadMediaPresenter?.fetchPreviousTitleAndDescription(0)
|
uploadMediaPresenter.fetchPreviousTitleAndDescription(0)
|
||||||
verify(view)?.setTitleAndDescription(ArgumentMatchers.anyString(),ArgumentMatchers.any())
|
verify(view).setTitleAndDescription(ArgumentMatchers.anyString(), ArgumentMatchers.any())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -133,9 +147,10 @@ class UploadMediaPresenterTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun fetchPreviousImageAndTitleTestNegative() {
|
fun fetchPreviousImageAndTitleTestNegative() {
|
||||||
Mockito.`when`(repository?.getPreviousUploadItem(ArgumentMatchers.anyInt())).thenReturn(null)
|
whenever(repository.getPreviousUploadItem(ArgumentMatchers.anyInt()))
|
||||||
uploadMediaPresenter?.fetchPreviousTitleAndDescription(0)
|
.thenReturn(null)
|
||||||
verify(view)?.showMessage(ArgumentMatchers.anyInt(),ArgumentMatchers.anyInt())
|
uploadMediaPresenter.fetchPreviousTitleAndDescription(0)
|
||||||
|
verify(view).showMessage(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -143,9 +158,9 @@ class UploadMediaPresenterTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun handleBadImageBaseTestInvalidLocation() {
|
fun handleBadImageBaseTestInvalidLocation() {
|
||||||
uploadMediaPresenter?.handleBadImage(8)
|
uploadMediaPresenter.handleBadImage(8)
|
||||||
verify(repository)?.saveValue(ArgumentMatchers.anyString(),eq(false))
|
verify(repository).saveValue(ArgumentMatchers.anyString(), eq(false))
|
||||||
verify(view)?.showBadImagePopup(8)
|
verify(view).showBadImagePopup(8)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -153,8 +168,8 @@ class UploadMediaPresenterTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun handleBadImageBaseTestEmptyTitle() {
|
fun handleBadImageBaseTestEmptyTitle() {
|
||||||
uploadMediaPresenter?.handleBadImage(-3)
|
uploadMediaPresenter.handleBadImage(-3)
|
||||||
verify(view)?.showMessage(ArgumentMatchers.anyInt(),ArgumentMatchers.anyInt())
|
verify(view).showMessage(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -162,8 +177,8 @@ class UploadMediaPresenterTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun handleBadImageBaseTestFileNameExists() {
|
fun handleBadImageBaseTestFileNameExists() {
|
||||||
uploadMediaPresenter?.handleBadImage(-4)
|
uploadMediaPresenter.handleBadImage(-4)
|
||||||
verify(view)?.showDuplicatePicturePopup()
|
verify(view).showDuplicatePicturePopup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -172,8 +187,9 @@ class UploadMediaPresenterTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun showSimilarImageFragmentTest() {
|
fun showSimilarImageFragmentTest() {
|
||||||
uploadMediaPresenter?.showSimilarImageFragment(ArgumentMatchers.anyString(),ArgumentMatchers.anyString())
|
val similar: ImageCoordinates = mock()
|
||||||
verify(view)?.showSimilarImageFragment(ArgumentMatchers.anyString(),ArgumentMatchers.anyString())
|
uploadMediaPresenter.showSimilarImageFragment("original", "possible", similar)
|
||||||
|
verify(view).showSimilarImageFragment("original", "possible", similar)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -181,8 +197,8 @@ class UploadMediaPresenterTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun setUploadItemTest() {
|
fun setUploadItemTest() {
|
||||||
uploadMediaPresenter?.setUploadItem(0,uploadItem)
|
uploadMediaPresenter.setUploadItem(0, uploadItem)
|
||||||
verify(repository)?.updateUploadItem(0,uploadItem)
|
verify(repository).updateUploadItem(0, uploadItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
package fr.free.nrw.commons.upload
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.Context
|
|
||||||
import fr.free.nrw.commons.auth.SessionManager
|
|
||||||
import fr.free.nrw.commons.filepicker.UploadableFile
|
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore
|
|
||||||
import fr.free.nrw.commons.nearby.Place
|
|
||||||
import fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK
|
|
||||||
import io.reactivex.Single
|
|
||||||
import org.junit.After
|
|
||||||
import org.junit.Assert.assertTrue
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
import org.mockito.ArgumentMatchers.*
|
|
||||||
import org.mockito.InjectMocks
|
|
||||||
import org.mockito.Mock
|
|
||||||
import org.mockito.Mockito.`when`
|
|
||||||
import org.mockito.Mockito.mock
|
|
||||||
import org.mockito.MockitoAnnotations
|
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.util.*
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Named
|
|
||||||
|
|
||||||
|
|
||||||
class UploadModelTest {
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
@field:[Inject Named("licenses")]
|
|
||||||
internal var licenses: List<String>? = null
|
|
||||||
@Mock
|
|
||||||
@field:[Inject Named("default_preferences")]
|
|
||||||
internal var prefs: JsonKvStore? = null
|
|
||||||
@Mock
|
|
||||||
@field:[Inject Named("licenses_by_name")]
|
|
||||||
internal var licensesByName: Map<String, String>? = null
|
|
||||||
@Mock
|
|
||||||
internal var context: Context? = null
|
|
||||||
@Mock
|
|
||||||
internal var sessionManage: SessionManager? = null
|
|
||||||
@Mock
|
|
||||||
internal var fileUtilsWrapper: FileUtilsWrapper? = null
|
|
||||||
@Mock
|
|
||||||
internal var fileProcessor: FileProcessor? = null
|
|
||||||
@Mock
|
|
||||||
internal var imageProcessingService: ImageProcessingService? = null
|
|
||||||
|
|
||||||
@InjectMocks
|
|
||||||
var uploadModel: UploadModel? = null
|
|
||||||
|
|
||||||
@Before
|
|
||||||
@Throws(Exception::class)
|
|
||||||
fun setUp() {
|
|
||||||
MockitoAnnotations.initMocks(this)
|
|
||||||
|
|
||||||
`when`(context!!.applicationContext)
|
|
||||||
.thenReturn(mock(Application::class.java))
|
|
||||||
`when`(fileUtilsWrapper!!.getFileExt(anyString()))
|
|
||||||
.thenReturn("jpg")
|
|
||||||
`when`(fileUtilsWrapper!!.getSHA1(any(InputStream::class.java)))
|
|
||||||
.thenReturn("sha")
|
|
||||||
`when`(fileUtilsWrapper!!.getFileInputStream(anyString()))
|
|
||||||
.thenReturn(mock(FileInputStream::class.java))
|
|
||||||
`when`(fileUtilsWrapper!!.getGeolocationOfFile(anyString()))
|
|
||||||
.thenReturn("")
|
|
||||||
`when`(imageProcessingService!!.validateImage(any(UploadModel.UploadItem::class.java)))
|
|
||||||
.thenReturn(Single.just(IMAGE_OK))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
@Throws(Exception::class)
|
|
||||||
fun tearDown() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun receive() {
|
|
||||||
val preProcessImages = uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
preProcessImages.doOnComplete {
|
|
||||||
assertTrue(uploadModel!!.items.size == 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun getCurrentStep() {
|
|
||||||
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
assertTrue(uploadModel!!.currentStep == 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun getStepCount() {
|
|
||||||
val preProcessImages = uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
preProcessImages.doOnComplete {
|
|
||||||
assertTrue(uploadModel!!.stepCount == 4)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun getCount() {
|
|
||||||
val preProcessImages = uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
preProcessImages.doOnComplete {
|
|
||||||
assertTrue(uploadModel!!.count == 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun getUploads() {
|
|
||||||
val preProcessImages = uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
preProcessImages.doOnComplete {
|
|
||||||
assertTrue(uploadModel!!.uploads.size == 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getMediaList(): List<UploadableFile> {
|
|
||||||
val element = getElement()
|
|
||||||
val element2 = getElement()
|
|
||||||
var uriList: List<UploadableFile> = mutableListOf(element, element2)
|
|
||||||
return uriList
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getElement(): UploadableFile {
|
|
||||||
val mock = mock(UploadableFile::class.java)
|
|
||||||
`when`(mock.filePath).thenReturn(UUID.randomUUID().toString() + "/filePath.jpg")
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun buildContributions() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue