#3529 Captions/depictions are not saved to Commons (#3574)

* #3529 Captions/depictions are not saved to Commons - make copy of list of depictionEntityIds - uncomment editBaseDepictsProperty - refactor upload related classes

* #3529 Captions/depictions are not saved to Commons - fix wrong ArrayList usage

* #3529 Captions/depictions are not saved to Commons - fix test
This commit is contained in:
Seán Mac Gillicuddy 2020-03-25 10:42:29 +00:00 committed by GitHub
parent 587d97716a
commit 23b8c2e659
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 360 additions and 504 deletions

View file

@ -3,18 +3,12 @@ package fr.free.nrw.commons;
import android.net.Uri; import android.net.Uri;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.room.Entity; import androidx.room.Entity;
import androidx.room.PrimaryKey; import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.utils.CommonsDateUtil;
import org.apache.commons.lang3.StringUtils; import fr.free.nrw.commons.utils.MediaDataExtractorUtil;
import org.wikipedia.dataclient.mwapi.MwQueryPage;
import org.wikipedia.gallery.ExtMetadata;
import org.wikipedia.gallery.ImageInfo;
import org.wikipedia.page.PageTitle;
import java.text.ParseException; import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -23,10 +17,11 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import fr.free.nrw.commons.location.LatLng; import org.wikipedia.dataclient.mwapi.MwQueryPage;
import fr.free.nrw.commons.utils.CommonsDateUtil; import org.wikipedia.gallery.ExtMetadata;
import fr.free.nrw.commons.utils.MediaDataExtractorUtil; import org.wikipedia.gallery.ImageInfo;
import org.wikipedia.page.PageTitle;
@Entity @Entity
public class Media implements Parcelable { public class Media implements Parcelable {
@ -90,7 +85,7 @@ public class Media implements Parcelable {
* Ex: key = "en", value: "<caption in short in English>" * Ex: key = "en", value: "<caption in short in English>"
* key = "de" , value: "<caption in german>" * key = "de" , value: "<caption in german>"
*/ */
public HashMap<String, String> captions; public Map<String, String> captions;
public HashMap<String, String> tags = new HashMap<>(); public HashMap<String, String> tags = new HashMap<>();
@Nullable public LatLng coordinates; @Nullable public LatLng coordinates;
@ -126,7 +121,7 @@ public class Media implements Parcelable {
* @param dateUploaded Media date uploaded * @param dateUploaded Media date uploaded
* @param creator Media creator * @param creator Media creator
*/ */
public Media(Uri localUri, String imageUrl, String filename, HashMap<String, String> captions, String description, public Media(Uri localUri, String imageUrl, String filename, Map<String, String> captions, String description,
long dataLength, Date dateCreated, Date dateUploaded, String creator) { long dataLength, Date dateCreated, Date dateUploaded, String creator) {
this(); this();
this.localUri = localUri; this.localUri = localUri;
@ -395,7 +390,7 @@ public class Media implements Parcelable {
* *
* returns list of captions stored in hashmap * returns list of captions stored in hashmap
*/ */
public HashMap<String, String> getCaptions() { public Map<String, String> getCaptions() {
return captions; return captions;
} }

View file

@ -1,29 +1,25 @@
package fr.free.nrw.commons.contributions; package fr.free.nrw.commons.contributions;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.os.Parcel; import android.os.Parcel;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.StringDef; import androidx.annotation.StringDef;
import androidx.room.Entity; import androidx.room.Entity;
import androidx.room.PrimaryKey; import androidx.room.PrimaryKey;
import org.apache.commons.lang3.StringUtils;
import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.filepicker.UploadableFile; import fr.free.nrw.commons.filepicker.UploadableFile;
import fr.free.nrw.commons.settings.Prefs; import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.utils.ConfigUtils; import fr.free.nrw.commons.utils.ConfigUtils;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.SOURCE; import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
@Entity(tableName = "contribution") @Entity(tableName = "contribution")
public class Contribution extends Media { public class Contribution extends Media {
@ -83,7 +79,7 @@ public class Contribution extends Media {
public Contribution(Uri contentUri, String filename, Uri localUri, String imageUrl, Date dateCreated, public Contribution(Uri contentUri, String filename, Uri localUri, String imageUrl, Date dateCreated,
int state, long dataLength, Date dateUploaded, long transferred, int state, long dataLength, Date dateUploaded, long transferred,
String source, HashMap<String, String> captions, String description, String creator, boolean isMultiple, String source, Map<String, String> captions, String description, String creator, boolean isMultiple,
int width, int height, String license) { int width, int height, String license) {
super(localUri, imageUrl, filename, captions, description, dataLength, dateCreated, dateUploaded, creator); super(localUri, imageUrl, filename, captions, description, dataLength, dateCreated, dateUploaded, creator);
this.contentUri = contentUri; this.contentUri = contentUri;
@ -97,7 +93,7 @@ public class Contribution extends Media {
this.dateCreatedSource = ""; this.dateCreatedSource = "";
} }
public Contribution(Uri localUri, String imageUrl, String filename, HashMap<String, String> captions, String description, long dataLength, public Contribution(Uri localUri, String imageUrl, String filename, Map<String, String> captions, String description, long dataLength,
Date dateCreated, Date dateUploaded, String creator, String editSummary, ArrayList<String> depictionsEntityIds, String decimalCoords) { Date dateCreated, Date dateUploaded, String creator, String editSummary, ArrayList<String> depictionsEntityIds, String decimalCoords) {
super(localUri, imageUrl, filename, captions, description, dataLength, dateCreated, dateUploaded, creator); super(localUri, imageUrl, filename, captions, description, dataLength, dateCreated, dateUploaded, creator);
this.decimalCoords = decimalCoords; this.decimalCoords = decimalCoords;
@ -106,7 +102,7 @@ public class Contribution extends Media {
this.depictionsEntityIds = depictionsEntityIds; this.depictionsEntityIds = depictionsEntityIds;
} }
public Contribution(Uri localUri, String imageUrl, String filename, HashMap<String, String> captions, String description, long dataLength, public Contribution(Uri localUri, String imageUrl, String filename, Map<String, String> captions, String description, long dataLength,
Date dateCreated, Date dateUploaded, String creator, String editSummary, String decimalCoords, int state) { Date dateCreated, Date dateUploaded, String creator, String editSummary, String decimalCoords, int state) {
super(localUri, imageUrl, filename, captions, description, dataLength, dateCreated, dateUploaded, creator); super(localUri, imageUrl, filename, captions, description, dataLength, dateCreated, dateUploaded, creator);
this.decimalCoords = decimalCoords; this.decimalCoords = decimalCoords;

View file

@ -1,24 +1,8 @@
package fr.free.nrw.commons.di; package fr.free.nrw.commons.di;
import android.content.Context; import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.google.gson.Gson; import com.google.gson.Gson;
import org.wikipedia.csrf.CsrfTokenClient;
import org.wikipedia.dataclient.Service;
import org.wikipedia.dataclient.ServiceFactory;
import org.wikipedia.dataclient.WikiSite;
import org.wikipedia.json.GsonUtil;
import org.wikipedia.login.LoginClient;
import java.io.File;
import java.util.concurrent.TimeUnit;
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;
@ -32,14 +16,25 @@ import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
import fr.free.nrw.commons.mwapi.UserInterface; import fr.free.nrw.commons.mwapi.UserInterface;
import fr.free.nrw.commons.review.ReviewInterface; import fr.free.nrw.commons.review.ReviewInterface;
import fr.free.nrw.commons.upload.UploadInterface; import fr.free.nrw.commons.upload.UploadInterface;
import fr.free.nrw.commons.wikidata.WikidataInterface;
import fr.free.nrw.commons.upload.WikiBaseInterface; import fr.free.nrw.commons.upload.WikiBaseInterface;
import fr.free.nrw.commons.upload.depicts.DepictsInterface; import fr.free.nrw.commons.upload.depicts.DepictsInterface;
import fr.free.nrw.commons.upload.mediaDetails.CaptionInterface; import fr.free.nrw.commons.upload.mediaDetails.CaptionInterface;
import fr.free.nrw.commons.wikidata.WikidataInterface;
import java.io.File;
import java.util.concurrent.TimeUnit;
import javax.inject.Named;
import javax.inject.Singleton;
import okhttp3.Cache; import okhttp3.Cache;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor; import okhttp3.logging.HttpLoggingInterceptor;
import okhttp3.logging.HttpLoggingInterceptor.Level;
import org.wikipedia.csrf.CsrfTokenClient;
import org.wikipedia.dataclient.Service;
import org.wikipedia.dataclient.ServiceFactory;
import org.wikipedia.dataclient.WikiSite;
import org.wikipedia.json.GsonUtil;
import org.wikipedia.login.LoginClient;
import timber.log.Timber; import timber.log.Timber;
@Module @Module
@ -76,7 +71,7 @@ public class NetworkingModule {
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(message -> { HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(message -> {
Timber.tag("OkHttp").v(message); Timber.tag("OkHttp").v(message);
}); });
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); httpLoggingInterceptor.level(BuildConfig.DEBUG ? Level.BODY: HttpLoggingInterceptor.Level.BASIC);
return httpLoggingInterceptor; return httpLoggingInterceptor;
} }

View file

@ -13,17 +13,8 @@ import android.net.Uri;
import android.os.IBinder; import android.os.IBinder;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.text.TextUtils; import android.text.TextUtils;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import javax.inject.Inject;
import javax.inject.Singleton;
import fr.free.nrw.commons.HandlerService; import fr.free.nrw.commons.HandlerService;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.SessionManager; import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.contributions.Contribution;
@ -34,23 +25,26 @@ import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import javax.inject.Inject;
import javax.inject.Singleton;
import timber.log.Timber; import timber.log.Timber;
@Singleton @Singleton
public class UploadController { public class UploadController {
private UploadService uploadService; private UploadService uploadService;
private SessionManager sessionManager; private final SessionManager sessionManager;
private Context context; private final Context context;
private JsonKvStore store; private final JsonKvStore store;
public interface ContributionUploadProgress {
void onUploadStarted(Contribution contribution);
}
@Inject @Inject
public UploadController(SessionManager sessionManager, public UploadController(final SessionManager sessionManager,
Context context, final Context context,
JsonKvStore store) { final JsonKvStore store) {
this.sessionManager = sessionManager; this.sessionManager = sessionManager;
this.context = context; this.context = context;
this.store = store; this.store = store;
@ -59,13 +53,13 @@ public class UploadController {
private boolean isUploadServiceConnected; private boolean isUploadServiceConnected;
public ServiceConnection uploadServiceConnection = new ServiceConnection() { public ServiceConnection uploadServiceConnection = new ServiceConnection() {
@Override @Override
public void onServiceConnected(ComponentName componentName, IBinder binder) { public void onServiceConnected(final ComponentName componentName, final IBinder binder) {
uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder) binder).getService(); uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder) binder).getService();
isUploadServiceConnected = true; isUploadServiceConnected = true;
} }
@Override @Override
public void onServiceDisconnected(ComponentName componentName) { public void onServiceDisconnected(final ComponentName componentName) {
// this should never happen // this should never happen
isUploadServiceConnected = false; isUploadServiceConnected = false;
Timber.e(new RuntimeException("UploadService died but the rest of the process did not!")); Timber.e(new RuntimeException("UploadService died but the rest of the process did not!"));
@ -76,7 +70,7 @@ public class UploadController {
* Prepares the upload service. * Prepares the upload service.
*/ */
public void prepareService() { public void prepareService() {
Intent uploadServiceIntent = new Intent(context, UploadService.class); final Intent uploadServiceIntent = new Intent(context, UploadService.class);
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE); uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
context.startService(uploadServiceIntent); context.startService(uploadServiceIntent);
context.bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE); context.bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
@ -96,28 +90,18 @@ public class UploadController {
* *
* @param contribution the contribution object * @param contribution the contribution object
*/ */
public void startUpload(Contribution contribution) {
startUpload(contribution, c -> {});
}
/**
* Starts a new upload task.
*
* @param contribution the contribution object
* @param onComplete the progress tracker
*/
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
private void startUpload(final Contribution contribution, final ContributionUploadProgress onComplete) { public void startUpload(final Contribution contribution) {
//Set creator, desc, and license //Set creator, desc, and license
// If author name is enabled and set, use it // If author name is enabled and set, use it
if (store.getBoolean("useAuthorName", false)) { if (store.getBoolean("useAuthorName", false)) {
String authorName = store.getString("authorName", ""); final String authorName = store.getString("authorName", "");
contribution.setCreator(authorName); contribution.setCreator(authorName);
} }
if (TextUtils.isEmpty(contribution.getCreator())) { if (TextUtils.isEmpty(contribution.getCreator())) {
Account currentAccount = sessionManager.getCurrentAccount(); final Account currentAccount = sessionManager.getCurrentAccount();
if (currentAccount == null) { if (currentAccount == null) {
Timber.d("Current account is null"); Timber.d("Current account is null");
ViewUtil.showLongToast(context, context.getString(R.string.user_not_logged_in)); ViewUtil.showLongToast(context, context.getString(R.string.user_not_logged_in));
@ -135,23 +119,23 @@ public class UploadController {
contribution.setCaption(""); contribution.setCaption("");
} }
String license = store.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3); final String license = store.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
contribution.setLicense(license); contribution.setLicense(license);
uploadTask(contribution, onComplete); uploadTask(contribution);
} }
/** /**
* Initiates the upload task * Initiates the upload task
* @param contribution * @param contribution
* @param onComplete
* @return * @return
*/ */
private Disposable uploadTask(Contribution contribution, ContributionUploadProgress onComplete) { private Disposable uploadTask(final Contribution contribution) {
return Single.fromCallable(() -> makeUpload(contribution)) return Single.just(contribution)
.map(this::buildUpload)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(finalContribution -> onUploadCompleted(finalContribution, onComplete)); .subscribe(this::upload);
} }
/** /**
@ -159,71 +143,76 @@ public class UploadController {
* @param contribution * @param contribution
* @return * @return
*/ */
private Contribution makeUpload(Contribution contribution) { private Contribution buildUpload(final Contribution contribution) {
long length; final ContentResolver contentResolver = context.getContentResolver();
ContentResolver contentResolver = context.getContentResolver();
contribution.setDataLength(resolveDataLength(contentResolver, contribution));
final String mimeType = resolveMimeType(contentResolver, contribution);
if (mimeType != null) {
Timber.d("MimeType is: %s", mimeType);
contribution.setTag("mimeType", mimeType);
if(mimeType.startsWith("image/") && contribution.getDateCreated() == null){
contribution.setDateCreated(resolveDateTakenOrNow(contentResolver, contribution));
}
}
return contribution;
}
private String resolveMimeType(final ContentResolver contentResolver, final Contribution contribution) {
final String mimeType = (String) contribution.getTag("mimeType");
if (mimeType == null || TextUtils.isEmpty(mimeType) || mimeType.endsWith("*")) {
return contentResolver.getType(contribution.getLocalUri());
}
return mimeType;
}
private long resolveDataLength(final ContentResolver contentResolver, final Media contribution) {
try { try {
if (contribution.getDataLength() <= 0) { if (contribution.getDataLength() <= 0) {
Timber.d("UploadController/doInBackground, contribution.getLocalUri():%s", contribution.getLocalUri()); Timber.d("UploadController/doInBackground, contribution.getLocalUri():%s", contribution.getLocalUri());
AssetFileDescriptor assetFileDescriptor = contentResolver final AssetFileDescriptor assetFileDescriptor = contentResolver
.openAssetFileDescriptor(Uri.fromFile(new File(contribution.getLocalUri().getPath())), "r"); .openAssetFileDescriptor(Uri.fromFile(new File(contribution.getLocalUri().getPath())), "r");
if (assetFileDescriptor != null) { if (assetFileDescriptor != null) {
length = assetFileDescriptor.getLength(); final long length = assetFileDescriptor.getLength();
if (length == -1) { return length != -1 ? length
// Let us find out the long way! : countBytes(contentResolver.openInputStream(contribution.getLocalUri()));
length = countBytes(contentResolver
.openInputStream(contribution.getLocalUri()));
}
contribution.setDataLength(length);
} }
} }
} catch (IOException | NullPointerException | SecurityException e) { } catch (final IOException | NullPointerException | SecurityException e) {
Timber.e(e, "Exception occurred while uploading image"); Timber.e(e, "Exception occurred while uploading image");
} }
return contribution.getDataLength();
String mimeType = (String) contribution.getTag("mimeType");
boolean imagePrefix = false;
if (mimeType == null || TextUtils.isEmpty(mimeType) || mimeType.endsWith("*")) {
mimeType = contentResolver.getType(contribution.getLocalUri());
} }
if (mimeType != null) { private Date resolveDateTakenOrNow(final ContentResolver contentResolver, final Media contribution) {
contribution.setTag("mimeType", mimeType);
imagePrefix = mimeType.startsWith("image/");
Timber.d("MimeType is: %s", mimeType);
}
if (imagePrefix && contribution.getDateCreated() == null) {
Timber.d("local uri %s", contribution.getLocalUri()); Timber.d("local uri %s", contribution.getLocalUri());
Cursor cursor = contentResolver.query(contribution.getLocalUri(), try(final Cursor cursor = dateTakenCursor(contentResolver, contribution)) {
new String[]{MediaStore.Images.ImageColumns.DATE_TAKEN}, null, null, null);
if (cursor != null && cursor.getCount() != 0 && cursor.getColumnCount() != 0) { if (cursor != null && cursor.getCount() != 0 && cursor.getColumnCount() != 0) {
cursor.moveToFirst(); cursor.moveToFirst();
Date dateCreated = new Date(cursor.getLong(0)); final Date dateCreated = new Date(cursor.getLong(0));
Date epochStart = new Date(0); if (dateCreated.after(new Date(0))) {
if (dateCreated.equals(epochStart) || dateCreated.before(epochStart)) { return dateCreated;
// If date is incorrect (1st second of unix time) then set it to the current date
dateCreated = new Date();
}
contribution.setDateCreated(dateCreated);
cursor.close();
} else {
contribution.setDateCreated(new Date());
} }
} }
return contribution; return new Date();
}
}
private Cursor dateTakenCursor(final ContentResolver contentResolver, final Media contribution) {
return contentResolver.query(contribution.getLocalUri(),
new String[]{MediaStore.Images.ImageColumns.DATE_TAKEN}, null, null, null);
} }
/** /**
* When the contribution object is completely formed, the item is queued to the upload service * When the contribution object is completely formed, the item is queued to the upload service
* @param contribution * @param contribution
* @param onComplete
*/ */
private void onUploadCompleted(Contribution contribution, ContributionUploadProgress onComplete) { private void upload(final Contribution contribution) {
//Starts the upload. If commented out, user can proceed to next Fragment but upload doesn't happen //Starts the upload. If commented out, user can proceed to next Fragment but upload doesn't happen
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, contribution); uploadService.queue(UploadService.ACTION_UPLOAD_FILE, contribution);
onComplete.onUploadStarted(contribution);
} }
@ -234,9 +223,9 @@ public class UploadController {
* @return the number of bytes in {@code stream} * @return the number of bytes in {@code stream}
* @throws IOException if an I/O error occurs * @throws IOException if an I/O error occurs
*/ */
private long countBytes(InputStream stream) throws IOException { private long countBytes(final InputStream stream) throws IOException {
long count = 0; long count = 0;
BufferedInputStream bis = new BufferedInputStream(stream); final BufferedInputStream bis = new BufferedInputStream(stream);
while (bis.read() != -1) { while (bis.read() != -1) {
count++; count++;
} }

View file

@ -1,117 +0,0 @@
package fr.free.nrw.commons.upload;
import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import timber.log.Timber;
/**
* Holds a description of an item being uploaded by {@link UploadActivity}
*/
public class UploadMediaDetail {
private String languageCode;
private String descriptionText;
public String captionText;
private int selectedLanguageIndex = -1;
private boolean isManuallyAdded=false;
/**
* Formatting captions to the Wikibase format for sending labels
* @param uploadMediaDetails list of media Details
*/
public static HashMap<String, String> formatCaptions(List<UploadMediaDetail> uploadMediaDetails) {
HashMap<String, String> caption = new HashMap<>();
for (UploadMediaDetail uploadMediaDetail : uploadMediaDetails) {
caption.put(uploadMediaDetail.getLanguageCode(),uploadMediaDetail.getCaptionText());
}
return caption;
}
public String getCaptionText() {
return captionText;
}
public void setCaptionText(String captionText) {
this.captionText = captionText;
}
/**
* @return The language code ie. "en" or "fr"
*/
String getLanguageCode() {
return languageCode;
}
/**
* @param languageCode The language code ie. "en" or "fr"
*/
public void setLanguageCode(String languageCode) {
this.languageCode = languageCode;
}
String getDescriptionText() {
return descriptionText;
}
public void setDescriptionText(String descriptionText) {
this.descriptionText = descriptionText;
}
/**
* @return the index of the language selected in a spinner with {@link SpinnerLanguagesAdapter}
*/
int getSelectedLanguageIndex() {
return selectedLanguageIndex;
}
/**
* @param selectedLanguageIndex the index of the language selected in a spinner with {@link SpinnerLanguagesAdapter}
*/
void setSelectedLanguageIndex(int selectedLanguageIndex) {
this.selectedLanguageIndex = selectedLanguageIndex;
}
/**
* returns if the description was added manually (by the user, or we have added it programaticallly)
* @return
*/
public boolean isManuallyAdded() {
return isManuallyAdded;
}
/**
* sets to true if the description was manually added by the user
* @param manuallyAdded
*/
public void setManuallyAdded(boolean manuallyAdded) {
isManuallyAdded = manuallyAdded;
}
/**
* Formats the list of descriptions into the format Commons requires for uploads.
*
* @param descriptions the list of descriptions, description is ignored if text is null.
* @return a string with the pattern of {{en|1=descriptionText}}
*/
static String formatList(List<UploadMediaDetail> descriptions) {
StringBuilder descListString = new StringBuilder();
for (UploadMediaDetail description : descriptions) {
if (!description.isEmpty()) {
String individualDescription = String.format("{{%s|1=%s}}", description.getLanguageCode(),
description.getDescriptionText());
descListString.append(individualDescription);
}
}
return descListString.toString();
}
public boolean isEmpty() {
return descriptionText == null || descriptionText.isEmpty();
}
}

View file

@ -0,0 +1,66 @@
package fr.free.nrw.commons.upload
import fr.free.nrw.commons.nearby.Place
import java.util.*
/**
* Holds a description of an item being uploaded by [UploadActivity]
*/
data class UploadMediaDetail constructor(
/**
* @return The language code ie. "en" or "fr"
*/
/**
* @param languageCode The language code ie. "en" or "fr"
*/
var languageCode: String? = null,
var descriptionText: String = "",
var captionText: String = ""
) {
constructor(place: Place) : this(
Locale.getDefault().language,
place.longDescription,
place.name
)
/**
* @return the index of the language selected in a spinner with [SpinnerLanguagesAdapter]
*/
/**
* @param selectedLanguageIndex the index of the language selected in a spinner with [SpinnerLanguagesAdapter]
*/
var selectedLanguageIndex: Int = -1
/**
* returns if the description was added manually (by the user, or we have added it programaticallly)
* @return
*/
/**
* sets to true if the description was manually added by the user
* @param manuallyAdded
*/
var isManuallyAdded: Boolean = false
companion object {
/**
* Formatting captions to the Wikibase format for sending labels
* @param uploadMediaDetails list of media Details
*/
@JvmStatic
fun formatCaptions(uploadMediaDetails: List<UploadMediaDetail>) =
uploadMediaDetails.associate { it.languageCode to it.captionText }
/**
* Formats the list of descriptions into the format Commons requires for uploads.
*
* @param descriptions the list of descriptions, description is ignored if text is null.
* @return a string with the pattern of {{en|1=descriptionText}}
*/
@JvmStatic
fun formatList(descriptions: List<UploadMediaDetail>) =
descriptions.joinToString {
if (it.descriptionText.isNotEmpty())
"{{${it.languageCode}|1=${it.descriptionText}}}"
else
""
}
}
}

View file

@ -2,9 +2,7 @@ package fr.free.nrw.commons.upload;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.text.Editable;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
@ -107,36 +105,11 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter<UploadMediaDe
public void init(int position) { public void init(int position) {
UploadMediaDetail uploadMediaDetail = uploadMediaDetails.get(position); UploadMediaDetail uploadMediaDetail = uploadMediaDetails.get(position);
Timber.d("UploadMediaDetail is " + uploadMediaDetail); Timber.d("UploadMediaDetail is " + uploadMediaDetail);
if (!TextUtils.isEmpty(uploadMediaDetail.getCaptionText())) {
captionItemEditText.setText(uploadMediaDetail.getCaptionText()); captionItemEditText.setText(uploadMediaDetail.getCaptionText());
} else {
captionItemEditText.setText("");
}
if (!TextUtils.isEmpty(uploadMediaDetail.getDescriptionText())) {
descItemEditText.setText(uploadMediaDetail.getDescriptionText()); descItemEditText.setText(uploadMediaDetail.getDescriptionText());
} else {
descItemEditText.setText("");
}
captionItemEditText.addTextChangedListener(new TextWatcher() { captionItemEditText.addTextChangedListener(new AbstractTextWatcher(
@Override value -> eventListener.onEvent(value.length() != 0)) );
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (s.length() != 0) {
eventListener.onEvent(true);
} else eventListener.onEvent(false);
}
});
if (position == 0) { if (position == 0) {
captionItemEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, getInfoIcon(), captionItemEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, getInfoIcon(),
@ -174,13 +147,11 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter<UploadMediaDe
} }
captionItemEditText.addTextChangedListener(new AbstractTextWatcher( captionItemEditText.addTextChangedListener(new AbstractTextWatcher(
captionText -> uploadMediaDetails.get(position) captionText -> uploadMediaDetails.get(position).setCaptionText(captionText)));
.setCaptionText(captionText)));
initLanguageSpinner(position, uploadMediaDetail); initLanguageSpinner(position, uploadMediaDetail);
descItemEditText.addTextChangedListener(new AbstractTextWatcher( descItemEditText.addTextChangedListener(new AbstractTextWatcher(
descriptionText -> uploadMediaDetails.get(position) descriptionText -> uploadMediaDetails.get(position).setDescriptionText(descriptionText)));
.setDescriptionText(descriptionText)));
initLanguageSpinner(position, uploadMediaDetail); initLanguageSpinner(position, uploadMediaDetail);
//If the description was manually added by the user, it deserves focus, if not, let the user decide //If the description was manually added by the user, it deserves focus, if not, let the user decide

View file

@ -7,7 +7,6 @@ import androidx.annotation.Nullable;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.auth.SessionManager; import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.filepicker.MimeTypeMapWrapper;
import fr.free.nrw.commons.filepicker.UploadableFile; import fr.free.nrw.commons.filepicker.UploadableFile;
import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.nearby.Place; import fr.free.nrw.commons.nearby.Place;
@ -18,15 +17,17 @@ 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.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Singleton; import javax.inject.Singleton;
import org.jetbrains.annotations.NotNull;
import timber.log.Timber; import timber.log.Timber;
@Singleton @Singleton
@ -37,23 +38,23 @@ public class UploadModel {
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 final List<UploadItem> items = new ArrayList<>();
private CompositeDisposable compositeDisposable = new CompositeDisposable(); private final CompositeDisposable compositeDisposable = new CompositeDisposable();
private SessionManager sessionManager; private final SessionManager sessionManager;
private FileProcessor fileProcessor; private final FileProcessor fileProcessor;
private final ImageProcessingService imageProcessingService; private final ImageProcessingService imageProcessingService;
private List<String> selectedCategories; private List<String> selectedCategories;
private ArrayList<String> selectedDepictions; private ArrayList<String> selectedDepictions;
@Inject @Inject
UploadModel(@Named("licenses") List<String> licenses, UploadModel(@Named("licenses") final List<String> licenses,
@Named("default_preferences") JsonKvStore store, @Named("default_preferences") final JsonKvStore store,
@Named("licenses_by_name") Map<String, String> licensesByName, @Named("licenses_by_name") final Map<String, String> licensesByName,
Context context, final Context context,
SessionManager sessionManager, final SessionManager sessionManager,
FileProcessor fileProcessor, final FileProcessor fileProcessor,
ImageProcessingService imageProcessingService) { final ImageProcessingService imageProcessingService) {
this.licenses = licenses; this.licenses = licenses;
this.store = store; this.store = store;
this.license = store.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3); this.license = store.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
@ -80,31 +81,29 @@ public class UploadModel {
} }
public void setSelectedCategories(List<String> selectedCategories) { public void setSelectedCategories(List<String> selectedCategories) {
if (null == selectedCategories) { this.selectedCategories = newListOf(selectedCategories);
selectedCategories = new ArrayList<>();
}
this.selectedCategories = selectedCategories;
} }
/** /**
* pre process a one item at a time * pre process a one item at a time
*/ */
public Observable<UploadItem> preProcessImage(UploadableFile uploadableFile, public Observable<UploadItem> preProcessImage(final UploadableFile uploadableFile,
Place place, final Place place,
String source, final String source,
SimilarImageInterface similarImageInterface) { final SimilarImageInterface similarImageInterface) {
return Observable.just(getUploadItem(uploadableFile, place, source, similarImageInterface)); return Observable.just(
createAndAddUploadItem(uploadableFile, place, source, similarImageInterface));
} }
public Single<Integer> getImageQuality(UploadItem uploadItem) { public Single<Integer> getImageQuality(final UploadItem uploadItem) {
return imageProcessingService.validateImage(uploadItem); return imageProcessingService.validateImage(uploadItem);
} }
private UploadItem getUploadItem(UploadableFile uploadableFile, private UploadItem createAndAddUploadItem(final UploadableFile uploadableFile,
Place place, final Place place,
String source, final String source,
SimilarImageInterface similarImageInterface) { final SimilarImageInterface similarImageInterface) {
UploadableFile.DateTimeWithSource dateTimeWithSource = uploadableFile final UploadableFile.DateTimeWithSource dateTimeWithSource = uploadableFile
.getFileCreatedDate(context); .getFileCreatedDate(context);
long fileCreatedDate = -1; long fileCreatedDate = -1;
String createdTimestampSource = ""; String createdTimestampSource = "";
@ -113,23 +112,15 @@ 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);
ImageCoordinates imageCoordinates = fileProcessor final ImageCoordinates imageCoordinates = fileProcessor
.processFileCoordinates(similarImageInterface, uploadableFile.getFilePath()); .processFileCoordinates(similarImageInterface, uploadableFile.getFilePath());
UploadItem uploadItem = new UploadItem(uploadableFile.getContentUri(), final UploadItem uploadItem = new UploadItem(uploadableFile.getContentUri(),
Uri.parse(uploadableFile.getFilePath()), Uri.parse(uploadableFile.getFilePath()),
uploadableFile.getMimeType(context), source, imageCoordinates, 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);
if(uploadItem.uploadMediaDetails.isEmpty()) { uploadItem.getUploadMediaDetails().set(0, new UploadMediaDetail(place));
uploadItem.uploadMediaDetails.add(new UploadMediaDetail());
}
uploadItem.uploadMediaDetails.get(0).setDescriptionText(place.getLongDescription());
uploadItem.uploadMediaDetails.get(0).setLanguageCode("en");
String languageCode = Locale.getDefault().getLanguage();
uploadItem.uploadMediaDetails.get(0).setDescriptionText(place.getLongDescription());
uploadItem.uploadMediaDetails.get(0).setLanguageCode(languageCode);
uploadItem.uploadMediaDetails.get(0).setCaptionText(place.name);
} }
if (!items.contains(uploadItem)) { if (!items.contains(uploadItem)) {
items.add(uploadItem); items.add(uploadItem);
@ -153,7 +144,7 @@ public class UploadModel {
return license; return license;
} }
public void setSelectedLicense(String licenseName) { public void setSelectedLicense(final String licenseName) {
this.license = licensesByName.get(licenseName); this.license = licensesByName.get(licenseName);
store.putString(Prefs.DEFAULT_LICENSE, license); store.putString(Prefs.DEFAULT_LICENSE, license);
} }
@ -161,11 +152,11 @@ public class UploadModel {
public Observable<Contribution> buildContributions() { public Observable<Contribution> buildContributions() {
return Observable.fromIterable(items).map(item -> return Observable.fromIterable(items).map(item ->
{ {
Contribution contribution = new Contribution(item.mediaUri, null, final Contribution contribution = new Contribution(item.mediaUri, null,
item.getFileName(), item.uploadMediaDetails.size()!=0? UploadMediaDetail.formatCaptions(item.uploadMediaDetails):new HashMap<>(), item.getFileName(), item.uploadMediaDetails.size()!=0? UploadMediaDetail.formatCaptions(item.uploadMediaDetails):new HashMap<>(),
UploadMediaDetail.formatList(item.uploadMediaDetails), -1, UploadMediaDetail.formatList(item.uploadMediaDetails), -1,
null, null, sessionManager.getAuthorName(), null, null, sessionManager.getAuthorName(),
CommonsApplication.DEFAULT_EDIT_SUMMARY, selectedDepictions, item.gpsCoords.getDecimalCoords()); CommonsApplication.DEFAULT_EDIT_SUMMARY, new ArrayList<>(selectedDepictions), item.gpsCoords.getDecimalCoords());
if (item.place != null) { if (item.place != null) {
contribution.setWikiDataEntityId(item.place.getWikiDataEntityId()); contribution.setWikiDataEntityId(item.place.getWikiDataEntityId());
contribution.setWikiItemName(item.place.getName()); contribution.setWikiItemName(item.place.getName());
@ -196,8 +187,8 @@ public class UploadModel {
}); });
} }
public void deletePicture(String filePath) { public void deletePicture(final String filePath) {
Iterator<UploadItem> iterator = items.iterator(); final Iterator<UploadItem> iterator = items.iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
if (iterator.next().mediaUri.toString().contains(filePath)) { if (iterator.next().mediaUri.toString().contains(filePath)) {
iterator.remove(); iterator.remove();
@ -213,20 +204,22 @@ public class UploadModel {
return items; return items;
} }
public void updateUploadItem(int index, UploadItem uploadItem) { public void updateUploadItem(final int index, final UploadItem uploadItem) {
UploadItem uploadItem1 = items.get(index); final UploadItem uploadItem1 = items.get(index);
uploadItem1.setMediaDetails(uploadItem.uploadMediaDetails); uploadItem1.setMediaDetails(uploadItem.uploadMediaDetails);
uploadItem1.setTitle(uploadItem.title); uploadItem1.setTitle(uploadItem.title);
} }
public void setSelectedDepictions(List<String> selectedDepictions) { public void setSelectedDepictions(final List<String> selectedDepictions) {
if (null == selectedDepictions) { this.selectedDepictions = newListOf(selectedDepictions);
selectedDepictions = new ArrayList<>();
}
this.selectedDepictions = (ArrayList<String>) selectedDepictions;
} }
public void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex) { @NotNull
private <T> ArrayList<T> newListOf(final List<T> items) {
return items != null ? new ArrayList<>(items) : new ArrayList<>();
}
public void useSimilarPictureCoordinates(final ImageCoordinates imageCoordinates, final int uploadItemIndex) {
fileProcessor.useImageCoords(imageCoordinates); fileProcessor.useImageCoords(imageCoordinates);
items.get(uploadItemIndex).setGpsCoords(imageCoordinates); items.get(uploadItemIndex).setGpsCoords(imageCoordinates);
} }
@ -239,29 +232,23 @@ public class UploadModel {
private final String mimeType; private final String mimeType;
private final String source; private final String source;
private ImageCoordinates gpsCoords; private ImageCoordinates gpsCoords;
public void setGpsCoords(ImageCoordinates gpsCoords) {
this.gpsCoords = gpsCoords;
}
private Title title; private Title title;
private List<UploadMediaDetail> uploadMediaDetails; private List<UploadMediaDetail> uploadMediaDetails;
private Place place; private final Place place;
private long createdTimestamp; private final long createdTimestamp;
private String createdTimestampSource; private final String createdTimestampSource;
private BehaviorSubject<Integer> imageQuality; private final BehaviorSubject<Integer> imageQuality;
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
UploadItem(Uri originalContentUri, UploadItem(final Uri originalContentUri,
Uri mediaUri, String mimeType, String source, ImageCoordinates gpsCoords, final Uri mediaUri, final String mimeType, final String source, final ImageCoordinates gpsCoords,
Place place, final Place place,
long createdTimestamp, final long createdTimestamp,
String createdTimestampSource) { final String createdTimestampSource) {
this.originalContentUri = originalContentUri; this.originalContentUri = originalContentUri;
this.createdTimestampSource = createdTimestampSource; this.createdTimestampSource = createdTimestampSource;
title = new Title(); title = new Title();
uploadMediaDetails = new ArrayList<>(); uploadMediaDetails = Collections.singletonList(new UploadMediaDetail());
uploadMediaDetails.add(new UploadMediaDetail()); uploadMediaDetails = new ArrayList<>(Arrays.asList(new UploadMediaDetail()));
this.place = place; this.place = place;
this.mediaUri = mediaUri; this.mediaUri = mediaUri;
this.mimeType = mimeType; this.mimeType = mimeType;
@ -303,7 +290,7 @@ public class UploadModel {
return this.imageQuality.getValue(); return this.imageQuality.getValue();
} }
public void setImageQuality(int imageQuality) { public void setImageQuality(final int imageQuality) {
this.imageQuality.onNext(imageQuality); this.imageQuality.onNext(imageQuality);
} }
@ -311,12 +298,12 @@ public class UploadModel {
return place; return place;
} }
public void setTitle(Title title) { public void setTitle(final Title title) {
this.title = title; this.title = title;
} }
public void setMediaDetails(List<UploadMediaDetail> uploadMediaDetails) { public void setMediaDetails(final List<UploadMediaDetail> uploadMediaDetails) {
this.uploadMediaDetails = uploadMediaDetails; this.uploadMediaDetails = uploadMediaDetails;
} }
@ -325,7 +312,7 @@ public class UploadModel {
} }
@Override @Override
public boolean equals(@Nullable Object obj) { public boolean equals(@Nullable final Object obj) {
if (!(obj instanceof UploadItem)) { if (!(obj instanceof UploadItem)) {
return false; return false;
} }
@ -345,6 +332,10 @@ public class UploadModel {
public String getFileName() { public String getFileName() {
return uploadMediaDetails.get(0).getCaptionText(); return uploadMediaDetails.get(0).getCaptionText();
} }
public void setGpsCoords(final ImageCoordinates gpsCoords) {
this.gpsCoords = gpsCoords;
} }
} }
}

View file

@ -13,6 +13,7 @@ import androidx.core.app.NotificationManagerCompat;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.text.ParseException;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -35,18 +36,8 @@ import fr.free.nrw.commons.utils.CommonsDateUtil;
import fr.free.nrw.commons.wikidata.WikidataEditService; import fr.free.nrw.commons.wikidata.WikidataEditService;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.Scheduler; import io.reactivex.Scheduler;
import io.reactivex.SingleObserver;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import timber.log.Timber; import timber.log.Timber;
public class UploadService extends HandlerService<Contribution> { public class UploadService extends HandlerService<Contribution> {
@ -275,7 +266,17 @@ public class UploadService extends HandlerService<Contribution> {
uploadStash.getFilekey()); uploadStash.getFilekey());
} }
}) })
.subscribe(uploadResult -> { .subscribe(
uploadResult -> onUpload(contribution, notificationTag, uploadResult),
throwable -> {
Timber.w(throwable, "Exception during upload");
notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS);
showFailedNotification(contribution);
});
}
private void onUpload(Contribution contribution, String notificationTag,
UploadResult uploadResult) throws ParseException {
Timber.d("Stash upload response 2 is %s", uploadResult.toString()); Timber.d("Stash upload response 2 is %s", uploadResult.toString());
notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS); notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS);
@ -304,8 +305,7 @@ public class UploadService extends HandlerService<Contribution> {
wikidataEditService.createClaimWithLogging( wikidataEditService.createClaimWithLogging(
wikiDataEntityId, wikiItemName, canonicalFilename,p18Value); wikiDataEntityId, wikiItemName, canonicalFilename,p18Value);
wikidataEditService.createLabelforWikidataEntity( wikidataEditService.createLabelforWikidataEntity(canonicalFilename, contribution.getCaptions());
wikiDataEntityId, canonicalFilename, contribution.getCaptions());
contribution.setFilename(canonicalFilename); contribution.setFilename(canonicalFilename);
contribution.setImageUrl(uploadResult.getImageinfo().getOriginalUrl()); contribution.setImageUrl(uploadResult.getImageinfo().getOriginalUrl());
contribution.setState(Contribution.STATE_COMPLETED); contribution.setState(Contribution.STATE_COMPLETED);
@ -317,11 +317,6 @@ public class UploadService extends HandlerService<Contribution> {
.observeOn(mainThreadScheduler) .observeOn(mainThreadScheduler)
.subscribe()); .subscribe());
} }
}, throwable -> {
Timber.w(throwable, "Exception during upload");
notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS);
showFailedNotification(contribution);
});
} }
@SuppressLint("StringFormatInvalid") @SuppressLint("StringFormatInvalid")

View file

@ -32,14 +32,12 @@ import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.nearby.Place; 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.DescriptionsAdapter;
import fr.free.nrw.commons.upload.UploadMediaDetail;
import fr.free.nrw.commons.upload.UploadMediaDetailAdapter;
import fr.free.nrw.commons.upload.ImageCoordinates; 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;
import fr.free.nrw.commons.upload.UploadMediaDetail;
import fr.free.nrw.commons.upload.UploadMediaDetailAdapter;
import fr.free.nrw.commons.upload.UploadModel; import fr.free.nrw.commons.upload.UploadModel;
import fr.free.nrw.commons.upload.UploadModel.UploadItem; import fr.free.nrw.commons.upload.UploadModel.UploadItem;
import fr.free.nrw.commons.utils.DialogUtil; import fr.free.nrw.commons.utils.DialogUtil;
@ -55,6 +53,8 @@ import javax.inject.Named;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import timber.log.Timber; import timber.log.Timber;
//import fr.free.nrw.commons.upload.DescriptionsAdapter;
public class UploadMediaDetailFragment extends UploadBaseFragment implements public class UploadMediaDetailFragment extends UploadBaseFragment implements
UploadMediaDetailsContract.View, UploadMediaDetailAdapter.EventListener { UploadMediaDetailsContract.View, UploadMediaDetailAdapter.EventListener {
@ -282,10 +282,6 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
@Override @Override
public void onImageProcessed(UploadItem uploadItem, Place place) { public void onImageProcessed(UploadItem uploadItem, Place place) {
this.uploadItem = uploadItem; this.uploadItem = uploadItem;
if (uploadItem.getFileName() != null) {
setDescriptionsInAdapter(uploadItem.getUploadMediaDetails());
}
descriptions = uploadItem.getUploadMediaDetails(); descriptions = uploadItem.getUploadMediaDetails();
photoViewBackgroundImage.setImageURI(uploadItem.getMediaUri()); photoViewBackgroundImage.setImageURI(uploadItem.getMediaUri());
setDescriptionsInAdapter(descriptions); setDescriptionsInAdapter(descriptions);
@ -306,10 +302,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
place.getName()), place.getName()),
() -> { () -> {
etTitle.setText(place.getName()); etTitle.setText(place.getName());
UploadMediaDetail description = new UploadMediaDetail(); descriptions = new ArrayList<>(Arrays.asList(new UploadMediaDetail()));
description.setLanguageCode("en");
description.setDescriptionText(place.getLongDescription());
descriptions = Arrays.asList(description);
setDescriptionsInAdapter(descriptions); setDescriptionsInAdapter(descriptions);
}, },
() -> { () -> {
@ -431,13 +424,6 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
} }
private void setDescriptionsInAdapter(List<UploadMediaDetail> uploadMediaDetails){ private void setDescriptionsInAdapter(List<UploadMediaDetail> uploadMediaDetails){
if(uploadMediaDetails==null){
uploadMediaDetails=new ArrayList<>();
}
if(uploadMediaDetails.size()==0){
uploadMediaDetails.add(new UploadMediaDetail());
}
uploadMediaDetailAdapter.setItems(uploadMediaDetails); uploadMediaDetailAdapter.setItems(uploadMediaDetails);
} }
} }

View file

@ -10,7 +10,6 @@ import com.google.gson.JsonObject;
import fr.free.nrw.commons.BuildConfig; import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.media.MediaClient;
import fr.free.nrw.commons.upload.mediaDetails.CaptionInterface; import fr.free.nrw.commons.upload.mediaDetails.CaptionInterface;
import fr.free.nrw.commons.utils.ConfigUtils; import fr.free.nrw.commons.utils.ConfigUtils;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
@ -26,7 +25,6 @@ import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Singleton; import javax.inject.Singleton;
import org.wikipedia.csrf.CsrfTokenClient; import org.wikipedia.csrf.CsrfTokenClient;
import org.wikipedia.dataclient.Service;
import timber.log.Timber; import timber.log.Timber;
/** /**
@ -46,42 +44,31 @@ public class WikidataEditService {
private final CaptionInterface captionInterface; private final CaptionInterface captionInterface;
private final WikiBaseClient wikiBaseClient; private final WikiBaseClient wikiBaseClient;
private final WikidataClient wikidataClient; private final WikidataClient wikidataClient;
private final MediaClient mediaClient;
private final CsrfTokenClient csrfTokenClient; private final CsrfTokenClient csrfTokenClient;
private final Service service;
@Inject @Inject
public WikidataEditService(Context context, public WikidataEditService(final Context context,
WikidataEditListener wikidataEditListener, final WikidataEditListener wikidataEditListener,
MediaClient mediaClient, @Named("default_preferences") final JsonKvStore directKvStore,
@Named("default_preferences") JsonKvStore directKvStore, final WikiBaseClient wikiBaseClient,
WikiBaseClient wikiBaseClient, final CaptionInterface captionInterface,
CaptionInterface captionInterface, final WikidataClient wikidataClient,
WikidataClient wikidataClient, @Named("commons-csrf") final CsrfTokenClient csrfTokenClient) {
@Named("commons-csrf") CsrfTokenClient csrfTokenClient,
@Named("commons-service") Service service) {
this.context = context; this.context = context;
this.wikidataEditListener = wikidataEditListener; this.wikidataEditListener = wikidataEditListener;
this.directKvStore = directKvStore; this.directKvStore = directKvStore;
this.captionInterface = captionInterface; this.captionInterface = captionInterface;
this.wikiBaseClient = wikiBaseClient; this.wikiBaseClient = wikiBaseClient;
this.mediaClient = mediaClient;
this.wikidataClient = wikidataClient; this.wikidataClient = wikidataClient;
this.csrfTokenClient = csrfTokenClient; this.csrfTokenClient = csrfTokenClient;
this.service = service;
} }
/** /**
* Create a P18 claim and log the edit with custom tag * Create a P18 claim and log the edit with custom tag
<<<<<<< HEAD
* *
* @param wikidataEntityId
* @param fileName
=======
* @param wikidataEntityId a unique id of each Wikidata items * @param wikidataEntityId a unique id of each Wikidata items
* @param fileName name of the file we will upload * @param fileName name of the file we will upload
* @param p18Value pic attribute of Wikidata item * @param p18Value pic attribute of Wikidata item
>>>>>>> origin/master
*/ */
public void createClaimWithLogging(String wikidataEntityId, String wikiItemName, String fileName, String p18Value) { public void createClaimWithLogging(String wikidataEntityId, String wikiItemName, String fileName, String p18Value) {
if (wikidataEntityId == null) { if (wikidataEntityId == null) {
@ -104,8 +91,8 @@ public class WikidataEditService {
return; return;
} }
editWikidataProperty(wikidataEntityId, wikiItemName, fileName); editWikidataProperty(wikidataEntityId, wikiItemName, fileName);;
//editWikiBaseDepictsProperty(wikidataEntityId, fileName); editWikiBaseDepictsProperty(wikidataEntityId, fileName);
} }
@ -122,7 +109,7 @@ public class WikidataEditService {
Timber.d("Upload successful with wiki data entity id as %s", wikidataEntityId); Timber.d("Upload successful with wiki data entity id as %s", wikidataEntityId);
Timber.d("Attempting to edit Wikidata property %s", wikidataEntityId); Timber.d("Attempting to edit Wikidata property %s", wikidataEntityId);
String propertyValue = getFileName(fileName); final String propertyValue = getFileName(fileName);
Timber.d("Entity id is %s and property value is %s", wikidataEntityId, propertyValue); Timber.d("Entity id is %s and property value is %s", wikidataEntityId, propertyValue);
wikidataClient.createClaim(wikidataEntityId, propertyValue) wikidataClient.createClaim(wikidataEntityId, propertyValue)
@ -148,7 +135,7 @@ public class WikidataEditService {
* @param fileName * @param fileName
*/ */
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
private void editWikiBaseDepictsProperty(String wikidataEntityId, String fileName) { private void editWikiBaseDepictsProperty(final String wikidataEntityId, final String fileName) {
wikiBaseClient.getFileEntityId(fileName) wikiBaseClient.getFileEntityId(fileName)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -166,37 +153,37 @@ public class WikidataEditService {
} }
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
private void addDepictsProperty(String entityId, String fileEntityId) { private void addDepictsProperty(String entityId, final String fileEntityId) {
if (ConfigUtils.isBetaFlavour()) { if (ConfigUtils.isBetaFlavour()) {
entityId = "Q10"; // Wikipedia:Sandbox (Q10) entityId = "Q10"; // Wikipedia:Sandbox (Q10)
} }
JsonObject value = new JsonObject(); final JsonObject value = new JsonObject();
value.addProperty("entity-type", "item"); value.addProperty("entity-type", "item");
value.addProperty("numeric-id", entityId.replace("Q", "")); value.addProperty("numeric-id", entityId.replace("Q", ""));
value.addProperty("id", entityId); value.addProperty("id", entityId);
JsonObject dataValue = new JsonObject(); final JsonObject dataValue = new JsonObject();
dataValue.add("value", value); dataValue.add("value", value);
dataValue.addProperty("type", "wikibase-entityid"); dataValue.addProperty("type", "wikibase-entityid");
JsonObject mainSnak = new JsonObject(); final JsonObject mainSnak = new JsonObject();
mainSnak.addProperty("snaktype", "value"); mainSnak.addProperty("snaktype", "value");
mainSnak.addProperty("property", BuildConfig.DEPICTS_PROPERTY); mainSnak.addProperty("property", BuildConfig.DEPICTS_PROPERTY);
mainSnak.add("datavalue", dataValue); mainSnak.add("datavalue", dataValue);
JsonObject claim = new JsonObject(); final JsonObject claim = new JsonObject();
claim.add("mainsnak", mainSnak); claim.add("mainsnak", mainSnak);
claim.addProperty("type", "statement"); claim.addProperty("type", "statement");
claim.addProperty("rank", "preferred"); claim.addProperty("rank", "preferred");
JsonArray claims = new JsonArray(); final JsonArray claims = new JsonArray();
claims.add(claim); claims.add(claim);
JsonObject jsonData = new JsonObject(); final JsonObject jsonData = new JsonObject();
jsonData.add("claims", claims); jsonData.add("claims", claims);
String data = jsonData.toString(); final String data = jsonData.toString();
Observable.defer((Callable<ObservableSource<Boolean>>) () -> Observable.defer((Callable<ObservableSource<Boolean>>) () ->
wikiBaseClient.postEditEntity(PAGE_ID_PREFIX + fileEntityId, data)) wikiBaseClient.postEditEntity(PAGE_ID_PREFIX + fileEntityId, data))
@ -253,18 +240,19 @@ public class WikidataEditService {
* Adding captions as labels after image is successfully uploaded * Adding captions as labels after image is successfully uploaded
*/ */
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
public void createLabelforWikidataEntity(String wikiDataEntityId, String fileName, Map<String, String> captions) { public void createLabelforWikidataEntity(final String fileName,
final Map<String, String> captions) {
Observable.fromCallable(() -> wikiBaseClient.getFileEntityId(fileName)) Observable.fromCallable(() -> wikiBaseClient.getFileEntityId(fileName))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(fileEntityId -> { .subscribe(fileEntityId -> {
if (fileEntityId != null) { if (fileEntityId != null) {
for (Map.Entry<String, String> entry : captions.entrySet()) { for (final Map.Entry<String, String> entry : captions.entrySet()) {
Map<String, String> caption = new HashMap<>(); final Map<String, String> caption = new HashMap<>();
caption.put(entry.getKey(), entry.getValue()); caption.put(entry.getKey(), entry.getValue());
try { try {
wikidataAddLabels(wikiDataEntityId, fileEntityId.toString(), caption); wikidataAddLabels(fileEntityId.toString(), caption);
} catch (Throwable throwable) { } catch (final Throwable throwable) {
throwable.printStackTrace(); throwable.printStackTrace();
} }
} }
@ -280,13 +268,12 @@ public class WikidataEditService {
/** /**
* Adds label to Wikidata using the fileEntityId and the edit token, obtained from csrfTokenClient * Adds label to Wikidata using the fileEntityId and the edit token, obtained from csrfTokenClient
* *
* @param wikiDataEntityId entityId for the current contribution
* @param fileEntityId * @param fileEntityId
* @param caption * @param caption
*/ */
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
private void wikidataAddLabels(String wikiDataEntityId, String fileEntityId, Map<String, String> caption) throws Throwable { private void wikidataAddLabels(final String fileEntityId, final Map<String, String> caption) {
Observable.fromCallable(() -> { Observable.fromCallable(() -> {
try { try {
return csrfTokenClient.getTokenBlocking(); return csrfTokenClient.getTokenBlocking();

View file

@ -2,12 +2,14 @@ package fr.free.nrw.commons.wikidata
import android.content.Context import android.content.Context
import com.nhaarman.mockitokotlin2.verifyZeroInteractions import com.nhaarman.mockitokotlin2.verifyZeroInteractions
import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.kvstore.JsonKvStore import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.wikidata.model.AddEditTagResponse import fr.free.nrw.commons.wikidata.model.AddEditTagResponse
import io.reactivex.Observable import io.reactivex.Observable
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mockito.ArgumentMatchers import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyString
import org.mockito.InjectMocks import org.mockito.InjectMocks
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.* import org.mockito.Mockito.*
@ -15,19 +17,19 @@ import org.mockito.MockitoAnnotations
class WikidataEditServiceTest { class WikidataEditServiceTest {
@Mock @Mock
internal var context: Context? = null internal lateinit var context: Context
@Mock
internal var wikidataEditListener: WikidataEditListener? = null
@Mock
internal var directKvStore: JsonKvStore? = null
@Mock
internal var wikidataClient: WikidataClient? = null
@Mock @Mock
internal var wikibaseClient: WikiBaseClient? = null internal lateinit var directKvStore: JsonKvStore
@Mock
internal lateinit var wikidataClient: WikidataClient
@Mock
internal lateinit var wikibaseClient: WikiBaseClient
@InjectMocks @InjectMocks
var wikidataEditService: WikidataEditService? = null lateinit var wikidataEditService: WikidataEditService
@Before @Before
@Throws(Exception::class) @Throws(Exception::class)
@ -37,40 +39,40 @@ class WikidataEditServiceTest {
@Test @Test
fun noClaimsWhenEntityIdIsNull() { fun noClaimsWhenEntityIdIsNull() {
wikidataEditService!!.createClaimWithLogging(null, null,"Test.jpg","") wikidataEditService.createClaimWithLogging(null, null,"Test.jpg","")
verifyZeroInteractions(wikidataClient!!) verifyZeroInteractions(wikidataClient)
} }
@Test @Test
fun noClaimsWhenFileNameIsNull() { fun noClaimsWhenFileNameIsNull() {
wikidataEditService!!.createClaimWithLogging("Q1", "Test", null,"") wikidataEditService.createClaimWithLogging("Q1", "Test", null,"")
verifyZeroInteractions(wikidataClient!!) verifyZeroInteractions(wikidataClient)
} }
@Test @Test
fun noClaimsWhenP18IsNotEmpty() { fun noClaimsWhenP18IsNotEmpty() {
wikidataEditService!!.createClaimWithLogging("Q1", "Test","Test.jpg","Previous.jpg") wikidataEditService.createClaimWithLogging("Q1", "Test","Test.jpg","Previous.jpg")
verifyZeroInteractions(wikidataClient!!) verifyZeroInteractions(wikidataClient)
} }
@Test @Test
fun noClaimsWhenLocationIsNotCorrect() { fun noClaimsWhenLocationIsNotCorrect() {
`when`(directKvStore!!.getBoolean("Picture_Has_Correct_Location", true)) whenever(directKvStore.getBoolean("Picture_Has_Correct_Location", true))
.thenReturn(false) .thenReturn(false)
wikidataEditService!!.createClaimWithLogging("Q1", "","Test.jpg","") wikidataEditService.createClaimWithLogging("Q1", "", "Test.jpg", "")
verifyZeroInteractions(wikidataClient!!) verifyZeroInteractions(wikidataClient)
} }
@Test @Test
fun createClaimWithLogging() { fun createClaimWithLogging() {
`when`(directKvStore!!.getBoolean("Picture_Has_Correct_Location", true)) whenever(directKvStore.getBoolean("Picture_Has_Correct_Location", true))
.thenReturn(true) .thenReturn(true)
`when`(wikidataClient!!.createClaim(ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) whenever(wikidataClient.createClaim(anyString(), anyString()))
.thenReturn(Observable.just(1L)) .thenReturn(Observable.just(1L))
`when`(wikidataClient!!.addEditTag(anyLong(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) whenever(wikidataClient.addEditTag(anyLong(), anyString(), anyString()))
.thenReturn(Observable.just(mock(AddEditTagResponse::class.java))) .thenReturn(Observable.just(mock(AddEditTagResponse::class.java)))
wikidataEditService!!.createClaimWithLogging("Q1", "Test","Test.jpg","") whenever(wikibaseClient.getFileEntityId(any())).thenReturn(Observable.just(1L))
verify(wikidataClient!!, times(1)) wikidataEditService.createClaimWithLogging("Q1", "", "Test.jpg", "")
.createClaim(ArgumentMatchers.anyString(), ArgumentMatchers.anyString()) verify(wikidataClient, times(1)).createClaim(anyString(), anyString())
} }
} }