#3408 Refactoring the FileProcessor and GPSExtractor classes (#3543)

* #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:
Seán Mac Gillicuddy 2020-03-20 14:18:14 +00:00 committed by GitHub
parent efc6fa6211
commit fb51fc618a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 450 additions and 834 deletions

View file

@ -2,26 +2,13 @@ package fr.free.nrw.commons.di;
import android.app.Activity;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.view.inputmethod.InputMethodManager;
import androidx.collection.LruCache;
import androidx.room.Room;
import com.github.varunpant.quadtree.QuadTree;
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.Provides;
import fr.free.nrw.commons.BuildConfig;
@ -41,6 +28,14 @@ import fr.free.nrw.commons.wikidata.WikidataEditListenerImpl;
import io.reactivex.Scheduler;
import io.reactivex.android.schedulers.AndroidSchedulers;
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.
@ -244,4 +239,9 @@ public class CommonsApplicationModule {
public ContributionDao providesContributionsDao() {
return appDatabase.getContributionDao();
}
@Provides
public ContentResolver providesContentResolver(Context context){
return context.getContentResolver();
}
}

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.repository;
import fr.free.nrw.commons.upload.ImageCoordinates;
import java.io.IOException;
import java.util.Comparator;
import java.util.List;
@ -202,4 +203,8 @@ public class UploadRemoteDataSource {
return null;
}
}
public void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex) {
uploadModel.useSimilarPictureCoordinates(imageCoordinates, uploadItemIndex);
}
}

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.repository;
import fr.free.nrw.commons.upload.ImageCoordinates;
import java.util.Comparator;
import java.util.List;
@ -271,4 +272,8 @@ public class UploadRepository {
public Place checkNearbyPlaces(double decLatitude, double decLongitude) {
return remoteDataSource.getNearbyPlaces(decLatitude, decLongitude);
}
public void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex) {
remoteDataSource.useSimilarPictureCoordinates(imageCoordinates, uploadItemIndex);
}
}

View file

@ -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();
}
}

View 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
}
}
}

View file

@ -68,9 +68,9 @@ public class FileUtils {
try {
ExifInterface exifInterface = new ExifInterface(filePath);
GPSExtractor imageObj = new GPSExtractor(exifInterface);
if (imageObj.imageCoordsExists) { // If image has geolocation information in its EXIF
return imageObj.getCoords();
ImageCoordinates imageObj = new ImageCoordinates(exifInterface);
if (imageObj.getDecimalCoords() != null) { // If image has geolocation information in its EXIF
return imageObj.getDecimalCoords();
} else {
return "";
}

View file

@ -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;
}
}

View file

@ -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() }
}

View file

@ -9,20 +9,16 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.Button;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
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.ButterKnife;
import butterknife.OnClick;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView;
import fr.free.nrw.commons.R;
import java.io.File;
/**
* Created by harisanker on 14/2/18.

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.upload;
public interface SimilarImageInterface {
void showSimilarImageFragment(String originalFilePath, String possibleFilePath);
void showSimilarImageFragment(String originalFilePath, String possibleFilePath,
ImageCoordinates similarImageCoordinates);
}

View file

@ -3,20 +3,7 @@ package fr.free.nrw.commons.upload;
import android.annotation.SuppressLint;
import android.content.Context;
import android.net.Uri;
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.Utils;
import fr.free.nrw.commons.auth.SessionManager;
@ -31,26 +18,25 @@ import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable;
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;
@Singleton
public class UploadModel {
private static UploadItem DUMMY = new UploadItem(
Uri.EMPTY, Uri.EMPTY,
"",
"",
GPSExtractor.DUMMY,
null,
-1L, "") {
};
private final JsonKvStore store;
private final List<String> licenses;
private final Context context;
private String license;
private final Map<String, String> licensesByName;
private List<UploadItem> items = new ArrayList<>();
private int currentStepIndex = 0;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private SessionManager sessionManager;
@ -95,19 +81,6 @@ public class UploadModel {
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
@ -127,8 +100,6 @@ public class UploadModel {
Place place,
String source,
SimilarImageInterface similarImageInterface) {
fileProcessor.initFileDetails(Objects.requireNonNull(uploadableFile.getFilePath()),
context.getContentResolver());
UploadableFile.DateTimeWithSource dateTimeWithSource = uploadableFile
.getFileCreatedDate(context);
long fileCreatedDate = -1;
@ -138,11 +109,11 @@ public class UploadModel {
createdTimestampSource = dateTimeWithSource.getSource();
}
Timber.d("File created date is %d", fileCreatedDate);
GPSExtractor gpsExtractor = fileProcessor
.processFileCoordinates(similarImageInterface, context);
ImageCoordinates imageCoordinates = fileProcessor
.processFileCoordinates(similarImageInterface, uploadableFile.getFilePath());
UploadItem uploadItem = new UploadItem(uploadableFile.getContentUri(),
Uri.parse(uploadableFile.getFilePath()),
uploadableFile.getMimeType(context), source, gpsExtractor, place, fileCreatedDate,
uploadableFile.getMimeType(context), source, imageCoordinates, place, fileCreatedDate,
createdTimestampSource);
if (place != null) {
uploadItem.title.setTitleText(place.name);
@ -158,14 +129,6 @@ public class UploadModel {
return uploadItem;
}
int getCurrentStep() {
return currentStepIndex + 1;
}
int getStepCount() {
return items.size() + 2;
}
public int getCount() {
return items.size();
}
@ -194,7 +157,7 @@ public class UploadModel {
item.getFileName(),
Description.formatList(item.descriptions), -1,
null, null, sessionManager.getAuthorName(),
CommonsApplication.DEFAULT_EDIT_SUMMARY, item.gpsCoords.getCoords());
CommonsApplication.DEFAULT_EDIT_SUMMARY, item.gpsCoords.getDecimalCoords());
if (item.place != null) {
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
@ -244,6 +207,11 @@ public class UploadModel {
uploadItem1.setTitle(uploadItem.title);
}
public void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex) {
fileProcessor.useImageCoords(imageCoordinates);
items.get(uploadItemIndex).setGpsCoords(imageCoordinates);
}
@SuppressWarnings("WeakerAccess")
public static class UploadItem {
@ -251,22 +219,22 @@ public class UploadModel {
private final Uri mediaUri;
private final String mimeType;
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 List<Description> descriptions;
private Place place;
private boolean visited;
private boolean error;
private long createdTimestamp;
private String createdTimestampSource;
private BehaviorSubject<Integer> imageQuality;
@SuppressLint("CheckResult")
UploadItem(Uri originalContentUri,
Uri mediaUri, String mimeType, String source, GPSExtractor gpsCoords,
Uri mediaUri, String mimeType, String source, ImageCoordinates gpsCoords,
Place place,
long createdTimestamp,
String createdTimestampSource) {
@ -287,38 +255,18 @@ public class UploadModel {
return createdTimestampSource;
}
public String getMimeType() {
return mimeType;
}
public String getSource() {
return source;
}
public GPSExtractor getGpsCoords() {
public ImageCoordinates getGpsCoords() {
return gpsCoords;
}
public boolean isSelected() {
return selected;
}
public boolean isFirst() {
return first;
}
public List<Description> getDescriptions() {
return descriptions;
}
public boolean isVisited() {
return visited;
}
public boolean isError() {
return error;
}
public long getCreatedTimestamp() {
return createdTimestamp;
}
@ -373,10 +321,9 @@ public class UploadModel {
}
//Travis is complaining :P
@Override
public int hashCode() {
return super.hashCode();
return mediaUri.hashCode();
}
}

View file

@ -1,5 +1,7 @@
package fr.free.nrw.commons.upload.mediaDetails;
import static fr.free.nrw.commons.utils.ImageUtils.getErrorMessageForResult;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
@ -12,30 +14,17 @@ import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatButton;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.recyclerview.widget.LinearLayoutManager;
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.ButterKnife;
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.Utils;
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.upload.Description;
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.Title;
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.ViewUtil;
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 static fr.free.nrw.commons.utils.ImageUtils.getErrorMessageForResult;
public class UploadMediaDetailFragment extends UploadBaseFragment implements
UploadMediaDetailsContract.View {
@ -231,14 +226,6 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
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
* @param titleStringID
@ -267,12 +254,14 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
}
@Override
public void showSimilarImageFragment(String originalFilePath, String possibleFilePath) {
public void showSimilarImageFragment(String originalFilePath, String possibleFilePath,
ImageCoordinates similarImageCoordinates) {
SimilarImageDialogFragment newFragment = new SimilarImageDialogFragment();
newFragment.setCallback(new SimilarImageDialogFragment.Callback() {
@Override
public void onPositiveResponse() {
Timber.d("positive response from similar image fragment");
presenter.useSimilarPictureCoordinates(similarImageCoordinates, callback.getIndexInViewFlipper(UploadMediaDetailFragment.this));
}
@Override

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.upload.mediaDetails;
import fr.free.nrw.commons.upload.ImageCoordinates;
import java.util.List;
import fr.free.nrw.commons.BasePresenter;
@ -48,6 +49,9 @@ public interface UploadMediaDetailsContract {
void setUploadItem(int index, UploadItem uploadItem);
void fetchPreviousTitleAndDescription(int indexInViewFlipper);
void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex);
}
}

View file

@ -1,15 +1,17 @@
package fr.free.nrw.commons.upload.mediaDetails;
import java.lang.reflect.Proxy;
import javax.inject.Inject;
import javax.inject.Named;
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;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.filepicker.UploadableFile;
import fr.free.nrw.commons.nearby.Place;
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.UploadModel.UploadItem;
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailsContract.UserActionListener;
@ -18,15 +20,11 @@ import io.reactivex.Observable;
import io.reactivex.Scheduler;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import java.lang.reflect.Proxy;
import javax.inject.Inject;
import javax.inject.Named;
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 {
private static final UploadMediaDetailsContract.View DUMMY = (UploadMediaDetailsContract.View) Proxy
@ -81,10 +79,10 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
.subscribe(uploadItem ->
{
view.onImageProcessed(uploadItem, place);
GPSExtractor gpsCoords = uploadItem.getGpsCoords();
view.showMapWithImageCoordinates(gpsCoords != null && gpsCoords.imageCoordsExists);
ImageCoordinates gpsCoords = uploadItem.getGpsCoords();
view.showMapWithImageCoordinates(gpsCoords != null && gpsCoords.getImageCoordsExists());
view.showProgress(false);
if (gpsCoords != null && gpsCoords.imageCoordsExists) {
if (gpsCoords != null && gpsCoords.getImageCoordsExists()) {
checkNearbyPlaces(uploadItem);
}
},
@ -159,7 +157,12 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
}
}
/**
@Override
public void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex) {
repository.useSimilarPictureCoordinates(imageCoordinates, uploadItemIndex);
}
/**
* handles image quality verifications
*
* @param imageResult
@ -200,12 +203,15 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
/**
* notifies the user that a similar image exists
*
* @param originalFilePath
* @param possibleFilePath
* @param similarImageCoordinates
*/
@Override
public void showSimilarImageFragment(String originalFilePath, String possibleFilePath) {
view.showSimilarImageFragment(originalFilePath, possibleFilePath);
public void showSimilarImageFragment(String originalFilePath, String possibleFilePath,
ImageCoordinates similarImageCoordinates) {
view.showSimilarImageFragment(originalFilePath, possibleFilePath,
similarImageCoordinates
);
}
}

View file

@ -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)
}
}
*/
}
}

View file

@ -1,5 +1,7 @@
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.nearby.Place
import fr.free.nrw.commons.repository.UploadRepository
@ -14,7 +16,6 @@ import org.junit.Test
import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@ -24,31 +25,32 @@ import org.mockito.MockitoAnnotations
*/
class UploadMediaPresenterTest {
@Mock
internal var repository: UploadRepository? = null
@Mock
internal var view: UploadMediaDetailsContract.View? = null
private var uploadMediaPresenter: UploadMediaPresenter? = null
internal lateinit var repository: UploadRepository
@Mock
private var uploadableFile: UploadableFile? = null
internal lateinit var view: UploadMediaDetailsContract.View
private lateinit var uploadMediaPresenter: UploadMediaPresenter
@Mock
private var place: Place? = null
private lateinit var uploadableFile: UploadableFile
@Mock
private var uploadItem: UploadModel.UploadItem? = null
private lateinit var place: Place
@Mock
private var title: Title? = null
private lateinit var uploadItem: UploadModel.UploadItem
@Mock
private var descriptions: List<Description>? = null
private lateinit var title: Title
private var testObservableUploadItem: Observable<UploadModel.UploadItem>? = null
private var testSingleImageResult: Single<Int>? = null
@Mock
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
@ -61,7 +63,7 @@ class UploadMediaPresenterTest {
testSingleImageResult = Single.just(1)
testScheduler = TestScheduler()
uploadMediaPresenter = UploadMediaPresenter(repository, testScheduler, testScheduler)
uploadMediaPresenter?.onAttachView(view)
uploadMediaPresenter.onAttachView(view)
}
/**
@ -69,12 +71,22 @@ class UploadMediaPresenterTest {
*/
@Test
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)
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)
whenever(
repository.preProcessImage(
ArgumentMatchers.any(UploadableFile::class.java),
ArgumentMatchers.any(Place::class.java),
ArgumentMatchers.anyString(),
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
fun verifyImageQualityTest() {
Mockito.`when`(repository?.getImageQuality(ArgumentMatchers.any(UploadModel.UploadItem::class.java))).thenReturn(testSingleImageResult)
Mockito.`when`(uploadItem?.imageQuality).thenReturn(ArgumentMatchers.anyInt())
uploadMediaPresenter?.verifyImageQuality(uploadItem)
verify(view)?.showProgress(true)
testScheduler?.triggerActions()
verify(view)?.showProgress(false)
whenever(repository.getImageQuality(ArgumentMatchers.any(UploadModel.UploadItem::class.java)))
.thenReturn(testSingleImageResult)
whenever(uploadItem.imageQuality).thenReturn(ArgumentMatchers.anyInt())
uploadMediaPresenter.verifyImageQuality(uploadItem)
verify(view).showProgress(true)
testScheduler.triggerActions()
verify(view).showProgress(false)
}
/**
@ -96,21 +109,21 @@ class UploadMediaPresenterTest {
@Test
fun handleImageResult() {
//Positive case test
uploadMediaPresenter?.handleImageResult(IMAGE_KEEP)
verify(view)?.onImageValidationSuccess()
uploadMediaPresenter.handleImageResult(IMAGE_KEEP)
verify(view).onImageValidationSuccess()
//Duplicate file name
uploadMediaPresenter?.handleImageResult(FILE_NAME_EXISTS)
verify(view)?.showDuplicatePicturePopup()
uploadMediaPresenter.handleImageResult(FILE_NAME_EXISTS)
verify(view).showDuplicatePicturePopup()
//Empty Title test
uploadMediaPresenter?.handleImageResult(EMPTY_TITLE)
verify(view)?.showMessage(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())
uploadMediaPresenter.handleImageResult(EMPTY_TITLE)
verify(view).showMessage(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())
//Bad Picture test
//Empty Title test
uploadMediaPresenter?.handleImageResult(-7)
verify(view)?.showBadImagePopup(ArgumentMatchers.anyInt())
uploadMediaPresenter.handleImageResult(-7)
verify(view).showBadImagePopup(ArgumentMatchers.anyInt())
}
@ -118,52 +131,54 @@ class UploadMediaPresenterTest {
* Test fetch previous image title when there was one
*/
@Test
fun fetchPreviousImageAndTitleTestPositive(){
Mockito.`when`(repository?.getPreviousUploadItem(ArgumentMatchers.anyInt())).thenReturn(uploadItem)
Mockito.`when`(uploadItem?.descriptions).thenReturn(descriptions)
Mockito.`when`(uploadItem?.title).thenReturn(title)
Mockito.`when`(title?.getTitleText()).thenReturn(ArgumentMatchers.anyString())
fun fetchPreviousImageAndTitleTestPositive() {
whenever(repository.getPreviousUploadItem(ArgumentMatchers.anyInt()))
.thenReturn(uploadItem)
whenever(uploadItem.descriptions).thenReturn(descriptions)
whenever(uploadItem.title).thenReturn(title)
whenever(title.getTitleText()).thenReturn(ArgumentMatchers.anyString())
uploadMediaPresenter?.fetchPreviousTitleAndDescription(0)
verify(view)?.setTitleAndDescription(ArgumentMatchers.anyString(),ArgumentMatchers.any())
uploadMediaPresenter.fetchPreviousTitleAndDescription(0)
verify(view).setTitleAndDescription(ArgumentMatchers.anyString(), ArgumentMatchers.any())
}
/**
* Test fetch previous image title when there was none
*/
@Test
fun fetchPreviousImageAndTitleTestNegative(){
Mockito.`when`(repository?.getPreviousUploadItem(ArgumentMatchers.anyInt())).thenReturn(null)
uploadMediaPresenter?.fetchPreviousTitleAndDescription(0)
verify(view)?.showMessage(ArgumentMatchers.anyInt(),ArgumentMatchers.anyInt())
fun fetchPreviousImageAndTitleTestNegative() {
whenever(repository.getPreviousUploadItem(ArgumentMatchers.anyInt()))
.thenReturn(null)
uploadMediaPresenter.fetchPreviousTitleAndDescription(0)
verify(view).showMessage(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())
}
/**
* Test bad image invalid location
*/
@Test
fun handleBadImageBaseTestInvalidLocation(){
uploadMediaPresenter?.handleBadImage(8)
verify(repository)?.saveValue(ArgumentMatchers.anyString(),eq(false))
verify(view)?.showBadImagePopup(8)
fun handleBadImageBaseTestInvalidLocation() {
uploadMediaPresenter.handleBadImage(8)
verify(repository).saveValue(ArgumentMatchers.anyString(), eq(false))
verify(view).showBadImagePopup(8)
}
/**
* Test bad image empty title
*/
@Test
fun handleBadImageBaseTestEmptyTitle(){
uploadMediaPresenter?.handleBadImage(-3)
verify(view)?.showMessage(ArgumentMatchers.anyInt(),ArgumentMatchers.anyInt())
fun handleBadImageBaseTestEmptyTitle() {
uploadMediaPresenter.handleBadImage(-3)
verify(view).showMessage(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())
}
/**
* Teste show file already exists
*/
@Test
fun handleBadImageBaseTestFileNameExists(){
uploadMediaPresenter?.handleBadImage(-4)
verify(view)?.showDuplicatePicturePopup()
fun handleBadImageBaseTestFileNameExists() {
uploadMediaPresenter.handleBadImage(-4)
verify(view).showDuplicatePicturePopup()
}
@ -171,18 +186,19 @@ class UploadMediaPresenterTest {
* Test show SimilarImageFragment
*/
@Test
fun showSimilarImageFragmentTest(){
uploadMediaPresenter?.showSimilarImageFragment(ArgumentMatchers.anyString(),ArgumentMatchers.anyString())
verify(view)?.showSimilarImageFragment(ArgumentMatchers.anyString(),ArgumentMatchers.anyString())
fun showSimilarImageFragmentTest() {
val similar: ImageCoordinates = mock()
uploadMediaPresenter.showSimilarImageFragment("original", "possible", similar)
verify(view).showSimilarImageFragment("original", "possible", similar)
}
/**
* Test set upload item
*/
@Test
fun setUploadItemTest(){
uploadMediaPresenter?.setUploadItem(0,uploadItem)
verify(repository)?.updateUploadItem(0,uploadItem)
fun setUploadItemTest() {
uploadMediaPresenter.setUploadItem(0, uploadItem)
verify(repository).updateUploadItem(0, uploadItem)
}
}

View file

@ -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() {
}
}