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

* #3529 Captions/depictions are not saved to Commons - update flow to update appropriate data

* #3529 Captions/depictions are not saved to Commons - fix invoking of setlabel

* #3529 Captions/depictions are not saved to Commons - fix unit tests

* #3529 Captions/depictions are not saved to Commons - use constant for @Named

* #3529 Captions/depictions are not saved to Commons - remove captions interface

* #3529 Captions/depictions are not saved to Commons - delete unused Contribution fields - enforce Single Responsibility by using PageContentsCreator

* #3529 Captions/depictions are not saved to Commons - prefix id with M - remove language from url and only add from Field

* #3529 Captions/depictions are not saved to Commons - make edits of depictions and captions sequential

* #3529 Captions/depictions are not saved to Commons - remove unused model fields

* #3529 Captions/depictions are not saved to Commons - weaken type of categories - copy list on Contribution creation

* #3529 Captions/depictions are not saved to Commons - mark Media fields private - weaken types - remove partly implemented fields

* #3529 Captions/depictions are not saved to Commons - add semi colon

* #3529 Captions/depictions are not saved to Commons - fix test
This commit is contained in:
Seán Mac Gillicuddy 2020-04-09 16:38:40 +01:00 committed by GitHub
parent 3f6d26c296
commit 628a6056e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 925 additions and 1271 deletions

View file

@ -11,9 +11,7 @@ import fr.free.nrw.commons.utils.CommonsDateUtil;
import fr.free.nrw.commons.utils.MediaDataExtractorUtil;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@ -30,62 +28,42 @@ public class Media implements Parcelable {
// Primary metadata fields
@Nullable
public Uri localUri;
public String thumbUrl;
public String imageUrl;
public String filename;
public String thumbnailTitle;
/**
private Uri localUri;
private String thumbUrl;
private String imageUrl;
private String filename;
private String thumbnailTitle;
/*
* Captions are a feature part of Structured data. They are meant to store short, multilingual descriptions about files
* This is a replacement of the previously used titles for images (titles were not multilingual)
* Also now captions replace the previous convention of using title for filename
*/
private String caption;
public String description; // monolingual description on input...
public String discussion;
long dataLength;
public Date dateCreated;
@Nullable public Date dateUploaded;
public int width;
public int height;
public String license;
public String licenseUrl;
public String creator;
private String description; // monolingual description on input...
private String discussion;
private long dataLength;
private Date dateCreated;
@Nullable private Date dateUploaded;
private String license;
private String licenseUrl;
private String creator;
/**
* Wikibase Identifier associated with media files
*/
public String pageId;
public ArrayList<String> categories; // as loaded at runtime?
private String pageId;
private List<String> categories; // as loaded at runtime?
/**
* Depicts is a feature part of Structured data. Multiple Depictions can be added for an image just like categories.
* However unlike categories depictions is multi-lingual
*/
public ArrayList<Map<String, String>> depictionList;
/**
* The above hashmap is fetched from API and to diplay in Explore
* However this list of depictions is for storing and retrieving depictions from local storage or cache
*/
public ArrayList<String> depictions;
public boolean requestedDeletion;
public HashMap<String, String> descriptions; // multilingual descriptions as loaded
/**
* This hasmap stores the list of multilingual captions, where
* key of the HashMap is the language and value is the caption in the corresponding language
* Ex: key = "en", value: "<caption in short in English>"
* key = "de" , value: "<caption in german>"
*/
public Map<String, String> captions;
public HashMap<String, String> tags = new HashMap<>();
@Nullable public LatLng coordinates;
private List<Map<String, String>> depictionList= new ArrayList<>();
private boolean requestedDeletion;
@Nullable private LatLng coordinates;
/**
* Provides local constructor
*/
protected Media() {
this.categories = new ArrayList<>();
this.depictions = new ArrayList<>();
this.descriptions = new HashMap<>();
this.captions = new HashMap<>();
public Media() {
}
/**
@ -94,7 +72,6 @@ public class Media implements Parcelable {
* @param filename Media filename
*/
public Media(String filename) {
this();
this.filename = filename;
}
@ -103,29 +80,35 @@ public class Media implements Parcelable {
* @param localUri Media URI
* @param imageUrl Media image URL
* @param filename Media filename
* @param captions Media captions
* @param description Media description
* @param dataLength Media date length
* @param dateCreated Media creation date
* @param dateUploaded Media date uploaded
* @param creator Media creator
*/
public Media(Uri localUri, String imageUrl, String filename, Map<String, String> captions, String description,
public Media(Uri localUri, String imageUrl, String filename,
String description,
long dataLength, Date dateCreated, Date dateUploaded, String creator) {
this();
this.localUri = localUri;
this.thumbUrl = imageUrl;
this.imageUrl = imageUrl;
this.filename = filename;
this.captions = captions;
this.description = description;
this.dataLength = dataLength;
this.dateCreated = dateCreated;
this.dateUploaded = dateUploaded;
this.creator = creator;
this.categories = new ArrayList<>();
this.depictions = new ArrayList<>();
this.descriptions = new HashMap<>();
}
public Media(Uri localUri, String filename,
String description, String creator, List<String> categories) {
this(localUri,null, filename,
description, -1, null, new Date(), creator);
this.categories = categories;
}
public Media(String title, Date date, String user) {
this(null, null, title, "", -1, date, date, user);
}
/**
@ -145,7 +128,7 @@ public class Media implements Parcelable {
ExtMetadata metadata = imageInfo.getMetadata();
if (metadata == null) {
Media media = new Media(null, imageInfo.getOriginalUrl(),
page.title(), new HashMap<>() , "", 0, null, null, null);
page.title(), "", 0, null, null, null);
if (!StringUtils.isBlank(imageInfo.getThumbUrl())) {
media.setThumbUrl(imageInfo.getThumbUrl());
}
@ -155,7 +138,6 @@ public class Media implements Parcelable {
Media media = new Media(null,
imageInfo.getOriginalUrl(),
page.title(),
new HashMap<>(),
"",
0,
safeParseDate(metadata.dateTime()),
@ -174,7 +156,7 @@ public class Media implements Parcelable {
language = "default";
}
media.setDescriptions(Collections.singletonMap(language, metadata.imageDescription()));
media.setDescription(metadata.imageDescription());
media.setCategories(MediaDataExtractorUtil.extractCategoriesFromList(metadata.getCategories()));
String latitude = metadata.getGpsLatitude();
String longitude = metadata.getGpsLongitude();
@ -212,31 +194,14 @@ public class Media implements Parcelable {
/**
*sets pageId for the current media object
*/
private void setPageId(String pageId) {
public void setPageId(String pageId) {
this.pageId = pageId;
}
public String getThumbUrl() {
return thumbUrl;
}
/**
* Gets tag of media
* @param key Media key
* @return Media tag
*/
public Object getTag(String key) {
return tags.get(key);
}
/**
* Modifies( or creates a) tag of media
* @param key Media key
* @param value Media value
*/
public void setTag(String key, String value) {
tags.put(key, value);
}
/**
* Gets media display title
* @return Media title
@ -340,22 +305,11 @@ public class Media implements Parcelable {
/**
* @return depictions associated with the current media
*/
public ArrayList<Map<String, String>> getDepiction() {
public List<Map<String, String>> getDepiction() {
return depictionList;
}
/**
* Captions are a feature part of Structured data. They are meant to store short, multilingual descriptions about files
* This is a replacement of the previously used titles for images (titles were not multilingual)
* Also now captions replace the previous convention of using title for filename
*
* key of the HashMap is the language and value is the caption in the corresponding language
*
* returns list of captions stored in hashmap
*/
public Map<String, String> getCaptions() {
return captions;
}
/**
* Sets the file description.
@ -423,38 +377,6 @@ public class Media implements Parcelable {
this.creator = creator;
}
/**
* Gets the width of the media.
* @return file width as an int
*/
public int getWidth() {
return width;
}
/**
* Sets the width of the media.
* @param width file width as an int
*/
public void setWidth(int width) {
this.width = width;
}
/**
* Gets the height of the media.
* @return file height as an int
*/
public int getHeight() {
return height;
}
/**
* Sets the height of the media.
* @param height file height as an int
*/
public void setHeight(int height) {
this.height = height;
}
/**
* Gets the license name of the file.
* @return license as a String
@ -506,15 +428,8 @@ public class Media implements Parcelable {
* @return file categories as an ArrayList of Strings
*/
@SuppressWarnings("unchecked")
public ArrayList<String> getCategories() {
return (ArrayList<String>) categories.clone(); // feels dirty
}
/**
* @return array list of depictions associated with the current media
*/
public ArrayList<String> getDepictions() {
return (ArrayList<String>) depictions.clone();
public List<String> getCategories() {
return categories;
}
/**
@ -525,43 +440,7 @@ public class Media implements Parcelable {
* @param categories file categories as a list of Strings
*/
public void setCategories(List<String> categories) {
this.categories.clear();
this.categories.addAll(categories);
}
public void setDepictions(List<String> depictions) {
this.depictions.clear();
this.depictions.addAll(depictions);
}
/**
* Modifies (or sets) media descriptions
* @param descriptions Media descriptions
*/
void setDescriptions(Map<String, String> descriptions) {
this.descriptions.clear();
this.descriptions.putAll(descriptions);
}
/**
* Gets media description in preferred language
* @param preferredLanguage Language preferred
* @return Description in preferred language
*/
public String getDescription(String preferredLanguage) {
if (descriptions.containsKey(preferredLanguage)) {
// See if the requested language is there.
return descriptions.get(preferredLanguage);
} else if (descriptions.containsKey("en")) {
// Ah, English. Language of the world, until the Chinese crush us.
return descriptions.get("en");
} else if (descriptions.containsKey("default")) {
// No languages marked...
return descriptions.get("default");
} else {
// FIXME: return the first available non-English description?
return "";
}
this.categories = categories;
}
@Nullable private static Date safeParseDate(String dateStr) {
@ -572,20 +451,19 @@ public class Media implements Parcelable {
}
}
/**
* Set requested deletion to true
* @param requestedDeletion
*/
public void setRequestedDeletion(){
requestedDeletion = true;
public void setRequestedDeletion(boolean requestedDeletion){
this.requestedDeletion = requestedDeletion;
}
/**
* Get the value of requested deletion
* @return boolean requestedDeletion
*/
public boolean getRequestedDeletion(){
public boolean isRequestedDeletion(){
return requestedDeletion;
}
@ -610,15 +488,29 @@ public class Media implements Parcelable {
this.caption = caption;
}
public void setCaptions(HashMap<String, String> captions) {
this.captions = captions;
/* Sets depictions for the current media obtained fro Wikibase API*/
public void setDepictionList(List<Map<String, String>> depictions) {
this.depictionList = depictions;
}
/**
* Sets depictions for the current media obtained fro Wikibase API
*/
public void setDepiction(ArrayList<Map<String, String>> depictions) {
this.depictionList = depictions;
public void setLocalUri(@Nullable final Uri localUri) {
this.localUri = localUri;
}
public void setImageUrl(final String imageUrl) {
this.imageUrl = imageUrl;
}
public void setDateUploaded(@Nullable final Date dateUploaded) {
this.dateUploaded = dateUploaded;
}
public void setLicenseUrl(final String licenseUrl) {
this.licenseUrl = licenseUrl;
}
public List<Map<String, String>> getDepictionList() {
return depictionList;
}
@Override
@ -645,8 +537,6 @@ public class Media implements Parcelable {
dest.writeLong(this.dataLength);
dest.writeLong(this.dateCreated != null ? this.dateCreated.getTime() : -1);
dest.writeLong(this.dateUploaded != null ? this.dateUploaded.getTime() : -1);
dest.writeInt(this.width);
dest.writeInt(this.height);
dest.writeString(this.license);
dest.writeString(this.licenseUrl);
dest.writeString(this.creator);
@ -654,8 +544,6 @@ public class Media implements Parcelable {
dest.writeStringList(this.categories);
dest.writeList(this.depictionList);
dest.writeByte(this.requestedDeletion ? (byte) 1 : (byte) 0);
dest.writeSerializable(this.descriptions);
dest.writeSerializable(this.tags);
dest.writeParcelable(this.coordinates, flags);
}
@ -673,8 +561,6 @@ public class Media implements Parcelable {
this.dateCreated = tmpDateCreated == -1 ? null : new Date(tmpDateCreated);
long tmpDateUploaded = in.readLong();
this.dateUploaded = tmpDateUploaded == -1 ? null : new Date(tmpDateUploaded);
this.width = in.readInt();
this.height = in.readInt();
this.license = in.readString();
this.licenseUrl = in.readString();
this.creator = in.readString();
@ -684,8 +570,6 @@ public class Media implements Parcelable {
this.categories=list;
in.readList(depictionList,null);
this.requestedDeletion = in.readByte() != 0;
this.descriptions = (HashMap<String, String>) in.readSerializable();
this.tags = (HashMap<String, String>) in.readSerializable();
this.coordinates = in.readParcelable(LatLng.class.getClassLoader());
}

View file

@ -55,9 +55,9 @@ public class MediaDataExtractor {
final String caption, final JsonObject depiction) {
media.setDiscussion(discussion);
media.setCaption(caption);
media.setDepiction(formatDepictions(depiction));
media.setDepictionList(formatDepictions(depiction));
if (deletionStatus) {
media.setRequestedDeletion();
media.setRequestedDeletion(true);
}
return media;
}

View file

@ -1,291 +1,156 @@
package fr.free.nrw.commons.contributions;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.content.Context;
import android.net.Uri;
import android.os.Parcel;
import androidx.annotation.NonNull;
import androidx.annotation.StringDef;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.filepicker.UploadableFile;
import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.utils.ConfigUtils;
import java.lang.annotation.Retention;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.upload.UploadMediaDetail;
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
import fr.free.nrw.commons.upload.WikidataPlace;
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.wikipedia.dataclient.mwapi.MwQueryLogEvent;
@Entity(tableName = "contribution")
public class Contribution extends Media {
//{{According to Exif data|2009-01-09}}
private static final String TEMPLATE_DATE_ACC_TO_EXIF = "{{According to Exif data|%s}}";
//2009-01-09 9 January 2009
private static final String TEMPLATE_DATA_OTHER_SOURCE = "%s";
// No need to be bitwise - they're mutually exclusive
public static final int STATE_COMPLETED = -1;
public static final int STATE_FAILED = 1;
public static final int STATE_QUEUED = 2;
public static final int STATE_IN_PROGRESS = 3;
@Retention(SOURCE)
@StringDef({SOURCE_CAMERA, SOURCE_GALLERY, SOURCE_EXTERNAL})
public @interface FileSource {}
public static final String SOURCE_CAMERA = "camera";
public static final String SOURCE_GALLERY = "gallery";
public static final String SOURCE_EXTERNAL = "external";
@PrimaryKey (autoGenerate = true)
@NonNull
public long _id;
public Uri contentUri;
public String source;
public String editSummary;
public int state;
public long transferred;
public String decimalCoords;
public boolean isMultiple;
public String wikiDataEntityId;
public String wikiItemName;
private String p18Value;
public Uri contentProviderUri;
public String dateCreatedSource;
private long _id;
private int state;
private long transferred;
private String decimalCoords;
private String dateCreatedSource;
private WikidataPlace wikidataPlace;
/**
* Each depiction loaded in depictions activity is associated with a wikidata entity id,
* this Id is in turn used to upload depictions to wikibase
*/
public ArrayList<String> depictionsEntityIds;
private List<DepictedItem> depictedItems = new ArrayList<>();
private String mimeType;
/**
* This hasmap stores the list of multilingual captions, where
* key of the HashMap is the language and value is the caption in the corresponding language
* Ex: key = "en", value: "<caption in short in English>"
* key = "de" , value: "<caption in german>"
*/
private Map<String, String> captions = new HashMap<>();
public Contribution(Uri contentUri, String filename, Uri localUri, String imageUrl, Date dateCreated,
int state, long dataLength, Date dateUploaded, long transferred,
String source, Map<String, String> captions, String description, String creator, boolean isMultiple,
int width, int height, String license) {
super(localUri, imageUrl, filename, captions, description, dataLength, dateCreated, dateUploaded, creator);
this.contentUri = contentUri;
this.state = state;
this.transferred = transferred;
this.source = source;
this.isMultiple = isMultiple;
this.width = width;
this.height = height;
this.license = license;
this.dateCreatedSource = "";
public Contribution() {
}
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) {
super(localUri, imageUrl, filename, captions, description, dataLength, dateCreated, dateUploaded, creator);
this.decimalCoords = decimalCoords;
this.editSummary = editSummary;
this.dateCreatedSource = "";
this.depictionsEntityIds = depictionsEntityIds;
public Contribution(final UploadItem item, final SessionManager sessionManager,
final List<DepictedItem> depictedItems, final List<String> categories) {
super(item.getMediaUri(),
item.getFileName(),
UploadMediaDetail.formatList(item.getUploadMediaDetails()),
sessionManager.getAuthorName(),
categories);
captions = UploadMediaDetail.formatCaptions(item.getUploadMediaDetails());
decimalCoords = item.getGpsCoords().getDecimalCoords();
dateCreatedSource = "";
this.depictedItems = depictedItems;
wikidataPlace = WikidataPlace.from(item.getPlace());
}
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) {
super(localUri, imageUrl, filename, captions, description, dataLength, dateCreated, dateUploaded, creator);
this.decimalCoords = decimalCoords;
this.editSummary = editSummary;
this.dateCreatedSource = "";
this.state=state;
public Contribution(final MwQueryLogEvent queryLogEvent, final String user) {
super(queryLogEvent.title(),queryLogEvent.date(), user);
decimalCoords = "";
dateCreatedSource = "";
state = STATE_COMPLETED;
}
public void setDateCreatedSource(String dateCreatedSource) {
public void setDateCreatedSource(final String dateCreatedSource) {
this.dateCreatedSource = dateCreatedSource;
}
public boolean getMultiple() {
return isMultiple;
}
public void setMultiple(boolean multiple) {
isMultiple = multiple;
public String getDateCreatedSource() {
return dateCreatedSource;
}
public long getTransferred() {
return transferred;
}
public void setTransferred(long transferred) {
public void setTransferred(final long transferred) {
this.transferred = transferred;
}
public String getEditSummary() {
return editSummary != null ? editSummary : CommonsApplication.DEFAULT_EDIT_SUMMARY;
}
public Uri getContentUri() {
return contentUri;
}
public void setContentUri(Uri contentUri) {
this.contentUri = contentUri;
}
public int getState() {
return state;
}
public void setState(int state) {
public void setState(final int state) {
this.state = state;
}
public void setDateUploaded(Date date) {
this.dateUploaded = date;
}
/**
* sets depiction entity ids for the given contribution
*/
public void setDepictions(ArrayList<String> depictionsEntityIds) {
this.depictionsEntityIds = depictionsEntityIds;
}
public String getPageContents(Context applicationContext) {
StringBuilder buffer = new StringBuilder();
buffer
.append("== {{int:filedesc}} ==\n")
.append("{{Information\n")
.append("|description=").append(getDescription()).append("\n")
.append("|source=").append("{{own}}\n")
.append("|author=[[User:").append(creator).append("|").append(creator).append("]]\n");
String templatizedCreatedDate = getTemplatizedCreatedDate();
if (!StringUtils.isBlank(templatizedCreatedDate)) {
buffer.append("|date=").append(templatizedCreatedDate);
}
buffer.append("}}").append("\n");
//Only add Location template (e.g. {{Location|37.51136|-77.602615}} ) if coords is not null
if (decimalCoords != null) {
buffer.append("{{Location|").append(decimalCoords).append("}}").append("\n");
}
buffer.append("== {{int:license-header}} ==\n")
.append(licenseTemplateFor(getLicense())).append("\n\n")
.append("{{Uploaded from Mobile|platform=Android|version=")
.append(ConfigUtils.getVersionNameWithSha(applicationContext)).append("}}\n");
if(categories!=null&&categories.size()!=0) {
for (int i = 0; i < categories.size(); i++) {
String category = categories.get(i);
buffer.append("\n[[Category:").append(category).append("]]");
}
}
else
buffer.append("{{subst:unc}}");
return buffer.toString();
}
/**
* Returns upload date in either TEMPLATE_DATE_ACC_TO_EXIF or TEMPLATE_DATA_OTHER_SOURCE
* @return
*/
private String getTemplatizedCreatedDate() {
if (dateCreated != null) {
java.text.SimpleDateFormat dateFormat = new java.text.SimpleDateFormat("yyyy-MM-dd");
if (UploadableFile.DateTimeWithSource.EXIF_SOURCE.equals(dateCreatedSource)) {
return String.format(Locale.ENGLISH, TEMPLATE_DATE_ACC_TO_EXIF, dateFormat.format(dateCreated)) + "\n";
} else {
return String.format(Locale.ENGLISH, TEMPLATE_DATA_OTHER_SOURCE, dateFormat.format(dateCreated)) + "\n";
}
}
return "";
}
@Override
public void setFilename(String filename) {
this.filename = filename;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
public Contribution() {
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
@NonNull
private String licenseTemplateFor(String license) {
switch (license) {
case Prefs.Licenses.CC_BY_3:
return "{{self|cc-by-3.0}}";
case Prefs.Licenses.CC_BY_4:
return "{{self|cc-by-4.0}}";
case Prefs.Licenses.CC_BY_SA_3:
return "{{self|cc-by-sa-3.0}}";
case Prefs.Licenses.CC_BY_SA_4:
return "{{self|cc-by-sa-4.0}}";
case Prefs.Licenses.CC0:
return "{{self|cc-zero}}";
}
throw new RuntimeException("Unrecognized license value: " + license);
}
public String getWikiDataEntityId() {
return wikiDataEntityId;
}
public String getWikiItemName() {
return wikiItemName;
}
/**
* When the corresponding wikidata entity is known as in case of nearby uploads, it can be set
* using the setter method
* @param wikiDataEntityId wikiDataEntityId
*/
public void setWikiDataEntityId(String wikiDataEntityId) {
this.wikiDataEntityId = wikiDataEntityId;
}
public void setWikiItemName(String wikiItemName) {
this.wikiItemName = wikiItemName;
}
public String getP18Value() {
return p18Value;
}
/**
* When the corresponding image property of wiki entity is known as in case of nearby uploads,
* it can be set using the setter method
* @param p18Value p18 value, image property of the wikidata item
*/
public void setP18Value(String p18Value) {
this.p18Value = p18Value;
}
public void setContentProviderUri(Uri contentProviderUri) {
this.contentProviderUri = contentProviderUri;
}
/**
* @return array list of entityids for the depictions
*/
public ArrayList<String> getDepictionsEntityIds() {
return depictionsEntityIds;
public List<DepictedItem> getDepictedItems() {
return depictedItems;
}
public void setWikidataPlace(final WikidataPlace wikidataPlace) {
this.wikidataPlace = wikidataPlace;
}
public WikidataPlace getWikidataPlace() {
return wikidataPlace;
}
public long get_id() {
return _id;
}
public void set_id(final long _id) {
this._id = _id;
}
public String getDecimalCoords() {
return decimalCoords;
}
public void setDecimalCoords(final String decimalCoords) {
this.decimalCoords = decimalCoords;
}
public void setDepictedItems(final List<DepictedItem> depictedItems) {
this.depictedItems = depictedItems;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public String getMimeType() {
return mimeType;
}
/**
* Captions are a feature part of Structured data. They are meant to store short, multilingual descriptions about files
* This is a replacement of the previously used titles for images (titles were not multilingual)
* Also now captions replace the previous convention of using title for filename
*
* key of the HashMap is the language and value is the caption in the corresponding language
*
* returns list of captions stored in hashmap
*/
public Map<String, String> getCaptions() {
return captions;
}
public void setCaptions(Map<String, String> captions) {
this.captions = captions;
}
@Override
@ -294,48 +159,34 @@ public class Contribution extends Media {
}
@Override
public void writeToParcel(Parcel dest, int flags) {
public void writeToParcel(final Parcel dest, final int flags) {
super.writeToParcel(dest, flags);
dest.writeLong(this._id);
dest.writeParcelable(this.contentUri, flags);
dest.writeString(this.source);
dest.writeString(this.editSummary);
dest.writeInt(this.state);
dest.writeLong(this.transferred);
dest.writeString(this.decimalCoords);
dest.writeByte(this.isMultiple ? (byte) 1 : (byte) 0);
dest.writeString(this.wikiDataEntityId);
dest.writeString(this.wikiItemName);
dest.writeString(this.p18Value);
dest.writeParcelable(this.contentProviderUri, flags);
dest.writeString(this.dateCreatedSource);
dest.writeLong(_id);
dest.writeInt(state);
dest.writeLong(transferred);
dest.writeString(decimalCoords);
dest.writeString(dateCreatedSource);
dest.writeSerializable((HashMap) captions);
}
protected Contribution(Parcel in) {
protected Contribution(final Parcel in) {
super(in);
this._id = in.readLong();
this.contentUri = in.readParcelable(Uri.class.getClassLoader());
this.source = in.readString();
this.editSummary = in.readString();
this.state = in.readInt();
this.transferred = in.readLong();
this.decimalCoords = in.readString();
this.isMultiple = in.readByte() != 0;
this.wikiDataEntityId = in.readString();
this.wikiItemName = in.readString();
this.p18Value = in.readString();
this.contentProviderUri = in.readParcelable(Uri.class.getClassLoader());
this.dateCreatedSource = in.readString();
_id = in.readLong();
state = in.readInt();
transferred = in.readLong();
decimalCoords = in.readString();
dateCreatedSource = in.readString();
captions = (HashMap<String, String>) in.readSerializable();
}
public static final Creator<Contribution> CREATOR = new Creator<Contribution>() {
@Override
public Contribution createFromParcel(Parcel source) {
public Contribution createFromParcel(final Parcel source) {
return new Contribution(source);
}
@Override
public Contribution[] newArray(int size) {
public Contribution[] newArray(final int size) {
return new Contribution[size];
}
};

View file

@ -1,19 +1,13 @@
package fr.free.nrw.commons.contributions;
import static fr.free.nrw.commons.upload.UploadService.EXTRA_FILES;
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.filepicker.DefaultCallback;
import fr.free.nrw.commons.filepicker.FilePicker;
@ -23,12 +17,11 @@ import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.upload.UploadActivity;
import fr.free.nrw.commons.utils.PermissionUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import static fr.free.nrw.commons.contributions.Contribution.SOURCE_CAMERA;
import static fr.free.nrw.commons.contributions.Contribution.SOURCE_GALLERY;
import static fr.free.nrw.commons.upload.UploadService.EXTRA_FILES;
import static fr.free.nrw.commons.upload.UploadService.EXTRA_SOURCE;
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
@Singleton
public class ContributionController {
@ -109,7 +102,7 @@ public class ContributionController {
@Override
public void onImagesPicked(@NonNull List<UploadableFile> imagesFiles, FilePicker.ImageSource source, int type) {
Intent intent = handleImagesPicked(activity, imagesFiles, getSourceFromImageSource(source));
Intent intent = handleImagesPicked(activity, imagesFiles);
activity.startActivity(intent);
}
});
@ -125,11 +118,9 @@ public class ContributionController {
* Attaches place object for nearby uploads
*/
private Intent handleImagesPicked(Context context,
List<UploadableFile> imagesFiles,
String source) {
List<UploadableFile> imagesFiles) {
Intent shareIntent = new Intent(context, UploadActivity.class);
shareIntent.setAction(ACTION_INTERNAL_UPLOADS);
shareIntent.putExtra(EXTRA_SOURCE, source);
shareIntent.putParcelableArrayListExtra(EXTRA_FILES, new ArrayList<>(imagesFiles));
Place place = defaultKvStore.getJson(PLACE_OBJECT, Place.class);
if (place != null) {
@ -139,13 +130,4 @@ public class ContributionController {
return shareIntent;
}
/**
* Get image upload source
*/
private String getSourceFromImageSource(FilePicker.ImageSource source) {
if (source.equals(FilePicker.ImageSource.CAMERA_IMAGE)) {
return SOURCE_CAMERA;
}
return SOURCE_GALLERY;
}
}

View file

@ -7,13 +7,10 @@ import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Transaction;
import androidx.room.Update;
import io.reactivex.disposables.Disposable;
import java.util.List;
import io.reactivex.Completable;
import io.reactivex.Single;
import java.util.List;
@Dao
public abstract class ContributionDao {
@ -40,9 +37,6 @@ public abstract class ContributionDao {
@Delete
public abstract Single<Integer> delete(Contribution contribution);
@Query("SELECT * from contribution WHERE contentProviderUri=:uri")
public abstract List<Contribution> getContributionWithUri(String uri);
@Query("SELECT * from contribution WHERE filename=:fileName")
public abstract List<Contribution> getContributionWithTitle(String fileName);

View file

@ -2,8 +2,6 @@ package fr.free.nrw.commons.contributions;
import static fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.text.TextUtils;
import android.view.View;
@ -60,7 +58,7 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
this.contribution = contribution;
fetchAndDisplayCaption(contribution);
this.position = position;
String imageSource = chooseImageSource(contribution.thumbUrl, contribution.getLocalUri());
String imageSource = chooseImageSource(contribution.getThumbUrl(), contribution.getLocalUri());
if (!TextUtils.isEmpty(imageSource)) {
final ImageRequest imageRequest =
ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageSource))

View file

@ -1,5 +1,9 @@
package fr.free.nrw.commons.contributions;
import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED;
import static fr.free.nrw.commons.contributions.MainActivity.CONTRIBUTIONS_TAB_POSITION;
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
@ -12,21 +16,12 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentManager.OnBackStackChangedListener;
import androidx.fragment.app.FragmentTransaction;
import fr.free.nrw.commons.MediaDataExtractor;
import io.reactivex.disposables.Disposable;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import butterknife.BindView;
import butterknife.ButterKnife;
import fr.free.nrw.commons.HandlerService;
@ -43,7 +38,6 @@ import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.location.LocationUpdateListener;
import fr.free.nrw.commons.media.MediaClient;
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
import fr.free.nrw.commons.media.MediaDetailPagerFragment.MediaDetailProvider;
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
@ -61,14 +55,11 @@ import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber;
import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED;
import static fr.free.nrw.commons.contributions.MainActivity.CONTRIBUTIONS_TAB_POSITION;
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
public class ContributionsFragment
extends CommonsDaggerSupportFragment
implements
@ -224,7 +215,7 @@ public class ContributionsFragment
@Override
public void fetchMediaUriFor(Contribution contribution) {
Timber.d("Fetching thumbnail for %s", contribution.filename);
Timber.d("Fetching thumbnail for %s", contribution.getFilename());
contributionsPresenter.fetchMediaDetails(contribution);
}
});

View file

@ -62,7 +62,7 @@ public class ContributionsListAdapter extends RecyclerView.Adapter<ContributionV
@Override
public long getItemId(int position) {
return contributions.get(position)._id;
return contributions.get(position).get_id();
}
public interface Callback {

View file

@ -1,46 +1,30 @@
package fr.free.nrw.commons.contributions;
import android.content.Context;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import fr.free.nrw.commons.MediaDataExtractor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.MediaDataExtractor;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.ContributionsContract.UserActionListener;
import fr.free.nrw.commons.db.AppDatabase;
import fr.free.nrw.commons.di.CommonsApplicationModule;
import fr.free.nrw.commons.mwapi.UserClient;
import fr.free.nrw.commons.utils.ExecutorUtils;
import fr.free.nrw.commons.utils.NetworkUtils;
import io.reactivex.Observable;
import io.reactivex.Scheduler;
import io.reactivex.SingleObserver;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber;
import static fr.free.nrw.commons.contributions.Contribution.STATE_COMPLETED;
/**
* The presenter class for Contributions
*/
@ -106,12 +90,7 @@ public class ContributionsPresenter implements UserActionListener {
.observeOn(mainThreadScheduler)
.doOnNext(mwQueryLogEvent -> Timber.d("Received image %s", mwQueryLogEvent.title()))
.filter(mwQueryLogEvent -> !mwQueryLogEvent.isDeleted()).doOnNext(mwQueryLogEvent -> Timber.d("Image %s passed filters", mwQueryLogEvent.title()))
.map(image -> {
Contribution contribution = new Contribution(null, null, image.title(),
new HashMap<>(), "", -1, image.date(), image.date(), user,
"", "", STATE_COMPLETED);
return contribution;
})
.map(image -> new Contribution(image, user))
.toList()
.subscribe(this::saveContributionsToDB, error -> {
Timber.e("Failed to fetch contributions: %s", error.getMessage());
@ -198,11 +177,11 @@ public class ContributionsPresenter implements UserActionListener {
@Override
public void fetchMediaDetails(Contribution contribution) {
compositeDisposable.add(mediaDataExtractor
.getMediaFromFileName(contribution.filename)
.getMediaFromFileName(contribution.getFilename())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(media -> {
contribution.thumbUrl=media.thumbUrl;
contribution.setThumbUrl(media.getThumbUrl());
updateContribution(contribution);
}));
}

View file

@ -7,9 +7,10 @@ import com.google.gson.reflect.TypeToken;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.location.LatLng;
import java.util.ArrayList;
import fr.free.nrw.commons.upload.WikidataPlace;
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@ -42,53 +43,74 @@ public class Converters {
}
@TypeConverter
public static String listObjectToString(ArrayList<String> objectList) {
return objectList == null ? null : getGson().toJson(objectList);
public static String listObjectToString(List<String> objectList) {
return writeObjectToString(objectList);
}
@TypeConverter
public static ArrayList<String> stringToArrayListObject(String objectList) {
return objectList == null ? null : getGson().fromJson(objectList,new TypeToken<ArrayList<String>>(){}.getType());
public static List<String> stringToListObject(String objectList) {
return readObjectWithTypeToken(objectList, new TypeToken<List<String>>() {});
}
@TypeConverter
public static String mapObjectToString(HashMap<String,String> objectList) {
return objectList == null ? null : getGson().toJson(objectList);
public static String mapObjectToString(Map<String,String> objectList) {
return writeObjectToString(objectList);
}
@TypeConverter
public static HashMap<String,String> stringToHashMap(String objectList) {
return objectList == null ? null : getGson().fromJson(objectList,new TypeToken<HashMap<String,String>>(){}.getType());
public static Map<String,String> stringToMap(String objectList) {
return readObjectWithTypeToken(objectList, new TypeToken<Map<String,String>>(){});
}
@TypeConverter
public static String latlngObjectToString(LatLng latlng) {
return latlng == null ? null : getGson().toJson(latlng);
return writeObjectToString(latlng);
}
@TypeConverter
public static LatLng stringToLatLng(String objectList) {
return objectList == null ? null : getGson().fromJson(objectList,LatLng.class);
return readObjectFromString(objectList,LatLng.class);
}
@TypeConverter
public static String listOfMapToString(ArrayList<Map<String,String>> listOfMaps) {
return listOfMaps == null ? null : getGson().toJson(listOfMaps);
public static String listOfMapToString(List<Map<String,String>> listOfMaps) {
return writeObjectToString(listOfMaps);
}
@TypeConverter
public static ArrayList<Map<String,String>> stringToListOfMap(String listOfMaps) {
return listOfMaps == null ? null :getGson().fromJson(listOfMaps,new TypeToken<ArrayList<Map<String,String>>>(){}.getType());
public static List<Map<String,String>> stringToListOfMap(String listOfMaps) {
return readObjectWithTypeToken(listOfMaps, new TypeToken<List<Map<String, String>>>() {});
}
@TypeConverter
public static String mapToString(Map<String,String> map) {
return map == null ? null : getGson().toJson(map);
public static String wikidataPlaceToString(WikidataPlace wikidataPlace) {
return writeObjectToString(wikidataPlace);
}
@TypeConverter
public static Map<String,String> stringToMap(String map) {
return map == null ? null :getGson().fromJson(map,new TypeToken<Map<String,String>>(){}.getType());
public static WikidataPlace stringToWikidataPlace(String wikidataPlace) {
return readObjectFromString(wikidataPlace, WikidataPlace.class);
}
@TypeConverter
public static String depictionListToString(List<DepictedItem> depictedItems) {
return writeObjectToString(depictedItems);
}
@TypeConverter
public static List<DepictedItem> stringToList(String depictedItems) {
return readObjectWithTypeToken(depictedItems, new TypeToken<List<DepictedItem>>() {});
}
private static String writeObjectToString(Object object) {
return object == null ? null : getGson().toJson(object);
}
private static<T> T readObjectFromString(String objectAsString, Class<T> clazz) {
return objectAsString == null ? null : getGson().fromJson(objectAsString, clazz);
}
private static <T> T readObjectWithTypeToken(String objectList, TypeToken<T> typeToken) {
return objectList == null ? null : getGson().fromJson(objectList, typeToken.getType());
}
}

View file

@ -1,22 +1,12 @@
package fr.free.nrw.commons.delete;
import static fr.free.nrw.commons.notification.NotificationHelper.NOTIFICATION_DELETE;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import androidx.appcompat.app.AlertDialog;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Locale;
import java.util.concurrent.Callable;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
@ -29,10 +19,16 @@ import io.reactivex.Single;
import io.reactivex.SingleSource;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Locale;
import java.util.concurrent.Callable;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import timber.log.Timber;
import static fr.free.nrw.commons.notification.NotificationHelper.NOTIFICATION_DELETE;
/**
* Refactored async task to Rx
*/
@ -104,7 +100,7 @@ public class DeleteHelper {
}
String creatorName = creator.replace(" (page does not exist)", "");
return pageEditClient.prependEdit(media.filename, fileDeleteString + "\n", summary)
return pageEditClient.prependEdit(media.getFilename(), fileDeleteString + "\n", summary)
.flatMap(result -> {
if (result) {
return pageEditClient.edit("Commons:Deletion_requests/" + media.getFilename(), subpageString + "\n", summary);

View file

@ -141,7 +141,7 @@ public class SubDepictionListPresenter implements SubDepictionListContract.UserA
this.queryList.addAll(mediaList);
view.onSuccess(mediaList);
for (DepictedItem m : mediaList) {
fetchThumbnailForEntityId(m.getEntityId(), size++);
fetchThumbnailForEntityId(m.getId(), size++);
}
}
}

View file

@ -197,8 +197,8 @@ public class WikidataItemDetailsActivity extends NavigationBaseActivity implemen
public static void startYourself(Context context, DepictedItem depictedItem) {
Intent intent = new Intent(context, WikidataItemDetailsActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra("wikidataItemName", depictedItem.getDepictsLabel());
intent.putExtra("entityId", depictedItem.getEntityId());
intent.putExtra("wikidataItemName", depictedItem.getName());
intent.putExtra("entityId", depictedItem.getId());
context.startActivity(intent);
}
}

View file

@ -18,7 +18,6 @@ import fr.free.nrw.commons.review.ReviewInterface;
import fr.free.nrw.commons.upload.UploadInterface;
import fr.free.nrw.commons.upload.WikiBaseInterface;
import fr.free.nrw.commons.upload.depicts.DepictsInterface;
import fr.free.nrw.commons.upload.mediaDetails.CaptionInterface;
import fr.free.nrw.commons.wikidata.WikidataInterface;
import java.io.File;
import java.util.concurrent.TimeUnit;
@ -163,12 +162,6 @@ public class NetworkingModule {
return ServiceFactory.get(commonsWikiSite, BuildConfig.COMMONS_URL, ReviewInterface.class);
}
@Provides
@Singleton
public CaptionInterface provideCaptionInterface(@Named(NAMED_WIKI_DATA_WIKI_SITE) WikiSite wikidataWikiSite) {
return ServiceFactory.get(wikidataWikiSite, BuildConfig.WIKIDATA_URL, CaptionInterface.class);
}
@Provides
@Singleton
public DepictsInterface provideDepictsInterface(@Named(NAMED_WIKI_DATA_WIKI_SITE) WikiSite wikidataWikiSite) {

View file

@ -12,7 +12,6 @@ import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@ -113,7 +112,6 @@ public class DepictsClient {
Media media = new Media(null,
getUrl(s.getTitle()),
s.getTitle(),
new HashMap<>(),
"",
0,
safeParseDate(s.getTimestamp()),

View file

@ -158,7 +158,7 @@ public class SearchDepictionsFragmentPresenter extends CommonsDaggerSupportFragm
view.onSuccess(mediaList);
offset=queryList.size();
for (DepictedItem m : mediaList) {
fetchThumbnailForEntityId(m.getEntityId(), size++);
fetchThumbnailForEntityId(m.getId(), size++);
}
}
}

View file

@ -78,7 +78,7 @@ public class SearchDepictionsRenderer extends Renderer<DepictedItem> {
@Override
public void render() {
DepictedItem item = getContent();
tvDepictionLabel.setText(item.getDepictsLabel());
tvDepictionLabel.setText(item.getName());
tvDepictionDesc.setText(item.getDescription());
imageView.setImageDrawable(getContext().getResources().getDrawable(R.drawable.ic_wikidata_logo_24dp));

View file

@ -1,10 +1,13 @@
package fr.free.nrw.commons.media;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import android.annotation.SuppressLint;
import android.graphics.drawable.Animatable;
import android.app.AlertDialog;
import android.content.Intent;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Animatable;
import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
@ -22,28 +25,17 @@ import android.widget.ScrollView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.drawee.controller.BaseControllerListener;
import com.facebook.drawee.controller.ControllerListener;
import com.facebook.drawee.view.SimpleDraweeView;
import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.request.ImageRequest;
import org.apache.commons.lang3.StringUtils;
import org.wikipedia.util.DateUtil;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import androidx.annotation.Nullable;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.controller.BaseControllerListener;
import com.facebook.drawee.controller.ControllerListener;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.drawee.view.SimpleDraweeView;
import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.request.ImageRequest;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.MediaDataExtractor;
import fr.free.nrw.commons.R;
@ -63,13 +55,15 @@ import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import javax.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import org.wikipedia.util.DateUtil;
import timber.log.Timber;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
public class MediaDetailFragment extends CommonsDaggerSupportFragment {
private boolean editable;
@ -555,8 +549,8 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
item.setOnClickListener(view -> {
DepictedItem depictedItem = new DepictedItem(depictionName, "", "", false, entityId);
Intent intent = new Intent(getContext(), WikidataItemDetailsActivity.class);
intent.putExtra("wikidataItemName", depictedItem.getDepictsLabel());
intent.putExtra("entityId", depictedItem.getEntityId());
intent.putExtra("wikidataItemName", depictedItem.getName());
intent.putExtra("entityId", depictedItem.getId());
getContext().startActivity(intent);
});
}
@ -609,7 +603,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
private String prettyDescription(Media media) {
// @todo use UI language when multilingual descs are available
String desc = media.getDescription(locale.getLanguage()).trim();
String desc = media.getDescription();
if (desc.equals("")) {
return getString(R.string.detail_description_empty);
} else {
@ -655,7 +649,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
}
private void checkDeletion(Media media){
if (media.getRequestedDeletion()){
if (media.isRequestedDeletion()){
delete.setVisibility(GONE);
nominatedForDeletion.setVisibility(VISIBLE);
} else if (!isCategoryImage) {

View file

@ -170,13 +170,12 @@ public class UploadRemoteDataSource {
*
* @param uploadableFile
* @param place
* @param source
* @param similarImageInterface
* @return
*/
public Observable<UploadItem> preProcessImage(UploadableFile uploadableFile, Place place,
String source, SimilarImageInterface similarImageInterface) {
return uploadModel.preProcessImage(uploadableFile, place, source, similarImageInterface);
SimilarImageInterface similarImageInterface) {
return uploadModel.preProcessImage(uploadableFile, place, similarImageInterface);
}
/**
@ -213,7 +212,7 @@ public class UploadRemoteDataSource {
*/
public void onDepictedItemClicked(DepictedItem depictedItem) {
depictModel.onDepictItemClicked(depictedItem);
uploadModel.onDepictItemClicked(depictedItem);
}
/**
@ -222,7 +221,7 @@ public class UploadRemoteDataSource {
*/
public List<DepictedItem> getSelectedDepictions() {
return depictModel.getSelectedDepictions();
return uploadModel.getSelectedDepictions();
}
/**
@ -233,14 +232,6 @@ public class UploadRemoteDataSource {
return depictModel.searchAllEntities(query);
}
public void setSelectedDepictions(List<String> selectedDepictions) {
uploadModel.setSelectedDepictions(selectedDepictions);
}
public List<String> depictionsEntityIdList() {
return depictModel.depictionsEntityIdList();
}
public void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex) {
uploadModel.useSimilarPictureCoordinates(imageCoordinates, uploadItemIndex);
}

View file

@ -115,10 +115,6 @@ public class UploadRepository {
remoteDataSource.setSelectedCategories(categoryStringList);
}
public void setSelectedDepictions(List<String> selectedDepictions) {
remoteDataSource.setSelectedDepictions(selectedDepictions);
}
/**
* handles the category selection/deselection
*
@ -180,14 +176,12 @@ public class UploadRepository {
*
* @param uploadableFile
* @param place
* @param source
* @param similarImageInterface
* @return
*/
public Observable<UploadItem> preProcessImage(UploadableFile uploadableFile, Place place,
String source, SimilarImageInterface similarImageInterface) {
return remoteDataSource
.preProcessImage(uploadableFile, place, source, similarImageInterface);
SimilarImageInterface similarImageInterface) {
return remoteDataSource.preProcessImage(uploadableFile, place, similarImageInterface);
}
/**
@ -294,10 +288,6 @@ public class UploadRepository {
return remoteDataSource.searchAllEntities(query);
}
public List<String> getDepictionsEntityIdList() {
return remoteDataSource.depictionsEntityIdList();
}
/**
* Returns nearest place matching the passed latitude and longitude
* @param decLatitude

View file

@ -0,0 +1,109 @@
package fr.free.nrw.commons.upload;
import android.content.Context;
import androidx.annotation.NonNull;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.filepicker.UploadableFile.DateTimeWithSource;
import fr.free.nrw.commons.settings.Prefs.Licenses;
import fr.free.nrw.commons.utils.ConfigUtils;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import javax.inject.Inject;
import org.apache.commons.lang3.StringUtils;
class PageContentsCreator {
//{{According to Exif data|2009-01-09}}
private static final String TEMPLATE_DATE_ACC_TO_EXIF = "{{According to Exif data|%s}}";
//2009-01-09 9 January 2009
private static final String TEMPLATE_DATA_OTHER_SOURCE = "%s";
private final Context context;
@Inject
public PageContentsCreator(Context context) {
this.context = context;
}
public String createFrom(Contribution contribution) {
StringBuilder buffer = new StringBuilder();
buffer
.append("== {{int:filedesc}} ==\n")
.append("{{Information\n")
.append("|description=").append(contribution.getDescription()).append("\n")
.append("|source=").append("{{own}}\n")
.append("|author=[[User:").append(contribution.getCreator()).append("|")
.append(contribution.getCreator()).append("]]\n");
String templatizedCreatedDate = getTemplatizedCreatedDate(
contribution.getDateCreated(), contribution.getDateCreatedSource());
if (!StringUtils.isBlank(templatizedCreatedDate)) {
buffer.append("|date=").append(templatizedCreatedDate);
}
buffer.append("}}").append("\n");
//Only add Location template (e.g. {{Location|37.51136|-77.602615}} ) if coords is not null
final String decimalCoords = contribution.getDecimalCoords();
if (decimalCoords != null) {
buffer.append("{{Location|").append(decimalCoords).append("}}").append("\n");
}
buffer.append("== {{int:license-header}} ==\n")
.append(licenseTemplateFor(contribution.getLicense())).append("\n\n")
.append("{{Uploaded from Mobile|platform=Android|version=")
.append(ConfigUtils.getVersionNameWithSha(context)).append("}}\n");
final List<String> categories = contribution.getCategories();
if (categories != null && categories.size() != 0) {
for (int i = 0; i < categories.size(); i++) {
buffer.append("\n[[Category:").append(categories.get(i)).append("]]");
}
} else {
buffer.append("{{subst:unc}}");
}
return buffer.toString();
}
/**
* Returns upload date in either TEMPLATE_DATE_ACC_TO_EXIF or TEMPLATE_DATA_OTHER_SOURCE
*
* @param dateCreated
* @param dateCreatedSource
* @return
*/
private String getTemplatizedCreatedDate(Date dateCreated, String dateCreatedSource) {
if (dateCreated != null) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
return String.format(Locale.ENGLISH,
isExif(dateCreatedSource) ? TEMPLATE_DATE_ACC_TO_EXIF : TEMPLATE_DATA_OTHER_SOURCE,
dateFormat.format(dateCreated)
) + "\n";
}
return "";
}
private boolean isExif(String dateCreatedSource) {
return DateTimeWithSource.EXIF_SOURCE.equals(dateCreatedSource);
}
@NonNull
private String licenseTemplateFor(String license) {
switch (license) {
case Licenses.CC_BY_3:
return "{{self|cc-by-3.0}}";
case Licenses.CC_BY_4:
return "{{self|cc-by-4.0}}";
case Licenses.CC_BY_SA_3:
return "{{self|cc-by-sa-3.0}}";
case Licenses.CC_BY_SA_4:
return "{{self|cc-by-sa-4.0}}";
case Licenses.CC0:
return "{{self|cc-zero}}";
}
throw new RuntimeException("Unrecognized license value: " + license);
}
}

View file

@ -1,5 +1,9 @@
package fr.free.nrw.commons.upload;
import static fr.free.nrw.commons.contributions.ContributionController.ACTION_INTERNAL_UPLOADS;
import static fr.free.nrw.commons.upload.UploadService.EXTRA_FILES;
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.ProgressDialog;
@ -10,7 +14,6 @@ import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import androidx.cardview.widget.CardView;
import androidx.fragment.app.Fragment;
@ -20,14 +23,6 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
@ -36,7 +31,6 @@ import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.LoginActivity;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.category.CategoriesModel;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.contributions.ContributionController;
import fr.free.nrw.commons.filepicker.UploadableFile;
import fr.free.nrw.commons.kvstore.JsonKvStore;
@ -48,18 +42,18 @@ import fr.free.nrw.commons.upload.depicts.DepictsFragment;
import fr.free.nrw.commons.upload.license.MediaLicenseFragment;
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment;
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.UploadMediaDetailFragmentCallback;
import fr.free.nrw.commons.upload.structure.depictions.DepictModel;
import fr.free.nrw.commons.utils.PermissionUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber;
import static fr.free.nrw.commons.contributions.ContributionController.ACTION_INTERNAL_UPLOADS;
import static fr.free.nrw.commons.upload.UploadService.EXTRA_FILES;
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
public class UploadActivity extends BaseActivity implements UploadContract.View, UploadBaseFragment.Callback {
@Inject
ContributionController contributionController;
@ -108,8 +102,6 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
private MediaLicenseFragment mediaLicenseFragment;
private ThumbnailsAdapter thumbnailsAdapter;
private String source;
private Place place;
private List<UploadableFile> uploadableFiles = Collections.emptyList();
private int currentSelectedPosition = 0;
@ -325,7 +317,7 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
fragments = new ArrayList<>();
for (UploadableFile uploadableFile : uploadableFiles) {
UploadMediaDetailFragment uploadMediaDetailFragment = new UploadMediaDetailFragment();
uploadMediaDetailFragment.setImageTobeUploaded(uploadableFile, source, place);
uploadMediaDetailFragment.setImageTobeUploaded(uploadableFile, place);
uploadMediaDetailFragment.setCallback(new UploadMediaDetailFragmentCallback() {
@Override
public void deletePictureAtIndex(int index) {
@ -387,16 +379,7 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
private void receiveInternalSharedItems() {
Intent intent = getIntent();
if (intent.hasExtra(UploadService.EXTRA_SOURCE)) {
source = intent.getStringExtra(UploadService.EXTRA_SOURCE);
} else {
source = Contribution.SOURCE_EXTERNAL;
}
Timber.d("Received intent %s with action %s and from source %s",
intent.toString(),
intent.getAction(),
source);
Timber.d("Received intent %s with action %s", intent.toString(), intent.getAction());
uploadableFiles = intent.getParcelableArrayListExtra(EXTRA_FILES);
Timber.i("Received multiple upload %s", uploadableFiles.size());

View file

@ -1,35 +1,36 @@
package fr.free.nrw.commons.upload;
import static fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF;
import android.content.Context;
import android.net.Uri;
import org.wikipedia.csrf.CsrfTokenClient;
import java.io.File;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.upload.UploadService.NotificationUpdateProgressListener;
import io.reactivex.Observable;
import java.io.File;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import static fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF;
import org.wikipedia.csrf.CsrfTokenClient;
@Singleton
public class UploadClient {
private final UploadInterface uploadInterface;
private final CsrfTokenClient csrfTokenClient;
private final PageContentsCreator pageContentsCreator;
@Inject
public UploadClient(UploadInterface uploadInterface, @Named(NAMED_COMMONS_CSRF) CsrfTokenClient csrfTokenClient) {
public UploadClient(UploadInterface uploadInterface,
@Named(NAMED_COMMONS_CSRF) CsrfTokenClient csrfTokenClient,
PageContentsCreator pageContentsCreator) {
this.uploadInterface = uploadInterface;
this.csrfTokenClient = csrfTokenClient;
this.pageContentsCreator = pageContentsCreator;
}
Observable<UploadResult> uploadFileToStash(Context context, String filename, File file,
@ -61,8 +62,8 @@ public class UploadClient {
try {
return uploadInterface
.uploadFileFromStash(csrfTokenClient.getTokenBlocking(),
contribution.getPageContents(context),
contribution.getEditSummary(),
pageContentsCreator.createFrom(contribution),
CommonsApplication.DEFAULT_EDIT_SUMMARY,
uniqueFileName,
fileKey).map(uploadResponse -> uploadResponse.getUpload());
} catch (Throwable throwable) {

View file

@ -115,10 +115,6 @@ public class UploadController {
contribution.setDescription("");
}
if (contribution.getCaption() == null) {
contribution.setCaption("");
}
final String license = store.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
contribution.setLicense(license);
@ -152,7 +148,7 @@ public class UploadController {
if (mimeType != null) {
Timber.d("MimeType is: %s", mimeType);
contribution.setTag("mimeType", mimeType);
contribution.setMimeType(mimeType);
if(mimeType.startsWith("image/") && contribution.getDateCreated() == null){
contribution.setDateCreated(resolveDateTakenOrNow(contentResolver, contribution));
}
@ -162,7 +158,7 @@ public class UploadController {
}
private String resolveMimeType(final ContentResolver contentResolver, final Contribution contribution) {
final String mimeType = (String) contribution.getTag("mimeType");
final String mimeType = contribution.getMimeType();
if (mimeType == null || TextUtils.isEmpty(mimeType) || mimeType.endsWith("*")) {
return contentResolver.getType(contribution.getLocalUri());
}

View file

@ -89,13 +89,13 @@ public class UploadDepictsRenderer extends Renderer<DepictedItem> {
public void render() {
DepictedItem item = getContent();
checkedView.setChecked(item.isSelected());
depictsLabel.setText(item.getDepictsLabel());
depictsLabel.setText(item.getName());
description.setText(item.getDescription());
if (!TextUtils.isEmpty(item.getImageUrl())) {
if (!item.getImageUrl().equals(NO_IMAGE_FOR_DEPICTION))
setImageView(Uri.parse(item.getImageUrl()), imageView);
}else{
listener.fetchThumbnailUrlForEntity(item.getEntityId(),item.getPosition());
listener.fetchThumbnailUrlForEntity(item.getId(),item.getPosition());
}
}

View file

@ -46,7 +46,7 @@ data class UploadMediaDetail constructor(
*/
@JvmStatic
fun formatCaptions(uploadMediaDetails: List<UploadMediaDetail>) =
uploadMediaDetails.associate { it.languageCode to it.captionText }
uploadMediaDetails.associate { it.languageCode to it.captionText }.filter { it.value.isNotBlank() }
/**
* Formats the list of descriptions into the format Commons requires for uploads.
@ -56,11 +56,7 @@ data class UploadMediaDetail constructor(
*/
@JvmStatic
fun formatList(descriptions: List<UploadMediaDetail>) =
descriptions.joinToString {
if (it.descriptionText.isNotEmpty())
"{{${it.languageCode}|1=${it.descriptionText}}}"
else
""
}
descriptions.filter { it.descriptionText.isNotEmpty() }
.joinToString { "{{${it.languageCode}|1=${it.descriptionText}}}" }
}
}

View file

@ -4,13 +4,13 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.Nullable;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.Contribution;
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.settings.Prefs;
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
import fr.free.nrw.commons.utils.ImageUtils;
import io.reactivex.Observable;
import io.reactivex.Single;
@ -18,9 +18,7 @@ import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.subjects.BehaviorSubject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@ -44,8 +42,8 @@ public class UploadModel {
private final SessionManager sessionManager;
private final FileProcessor fileProcessor;
private final ImageProcessingService imageProcessingService;
private List<String> selectedCategories;
private ArrayList<String> selectedDepictions;
private List<String> selectedCategories = new ArrayList<>();
private List<DepictedItem> selectedDepictions = new ArrayList<>();
@Inject
UploadModel(@Named("licenses") final List<String> licenses,
@ -71,17 +69,13 @@ public class UploadModel {
public void cleanUp() {
compositeDisposable.clear();
fileProcessor.cleanup();
this.items.clear();
if (this.selectedCategories != null) {
this.selectedCategories.clear();
}
if (this.selectedDepictions != null) {
this.selectedDepictions.clear();
}
items.clear();
selectedCategories.clear();
selectedDepictions.clear();
}
public void setSelectedCategories(List<String> selectedCategories) {
this.selectedCategories = newListOf(selectedCategories);
this.selectedCategories = selectedCategories;
}
/**
@ -89,10 +83,9 @@ public class UploadModel {
*/
public Observable<UploadItem> preProcessImage(final UploadableFile uploadableFile,
final Place place,
final String source,
final SimilarImageInterface similarImageInterface) {
return Observable.just(
createAndAddUploadItem(uploadableFile, place, source, similarImageInterface));
createAndAddUploadItem(uploadableFile, place, similarImageInterface));
}
public Single<Integer> getImageQuality(final UploadItem uploadItem) {
@ -101,7 +94,6 @@ public class UploadModel {
private UploadItem createAndAddUploadItem(final UploadableFile uploadableFile,
final Place place,
final String source,
final SimilarImageInterface similarImageInterface) {
final UploadableFile.DateTimeWithSource dateTimeWithSource = uploadableFile
.getFileCreatedDate(context);
@ -116,7 +108,7 @@ public class UploadModel {
.processFileCoordinates(similarImageInterface, uploadableFile.getFilePath());
final UploadItem uploadItem = new UploadItem(uploadableFile.getContentUri(),
Uri.parse(uploadableFile.getFilePath()),
uploadableFile.getMimeType(context), source, imageCoordinates, place, fileCreatedDate,
uploadableFile.getMimeType(context), imageCoordinates, place, fileCreatedDate,
createdTimestampSource);
if (place != null) {
uploadItem.getUploadMediaDetails().set(0, new UploadMediaDetail(place));
@ -151,29 +143,8 @@ public class UploadModel {
public Observable<Contribution> buildContributions() {
return Observable.fromIterable(items).map(item ->
{
final Contribution contribution = new Contribution(item.mediaUri, null,
item.getFileName(), item.uploadMediaDetails.size()!=0? UploadMediaDetail.formatCaptions(item.uploadMediaDetails):new HashMap<>(),
UploadMediaDetail.formatList(item.uploadMediaDetails), -1,
null, null, sessionManager.getAuthorName(),
CommonsApplication.DEFAULT_EDIT_SUMMARY, new ArrayList<>(selectedDepictions), item.gpsCoords.getDecimalCoords());
if (item.place != null) {
contribution.setWikiDataEntityId(item.place.getWikiDataEntityId());
contribution.setWikiItemName(item.place.getName());
// If item already has an image, we need to know it. We don't want to override existing image later
contribution.setP18Value(item.place.pic);
}
if (null == selectedCategories) {//Just a fail safe, this should never be null
selectedCategories = new ArrayList<>();
}
if (selectedDepictions == null) {
selectedDepictions = new ArrayList<>();
}
contribution.setCategories(selectedCategories);
contribution.setTag("mimeType", item.mimeType);
contribution.setSource(item.source);
contribution.setContentProviderUri(item.mediaUri);
contribution.setDateUploaded(new Date());
final Contribution contribution = new Contribution(
item, sessionManager, newListOf(selectedDepictions), newListOf(selectedCategories));
Timber.d("Created timestamp while building contribution is %s, %s",
item.getCreatedTimestamp(),
new Date(item.getCreatedTimestamp()));
@ -208,12 +179,16 @@ public class UploadModel {
uploadItem1.setMediaDetails(uploadItem.uploadMediaDetails);
}
public void setSelectedDepictions(final List<String> selectedDepictions) {
this.selectedDepictions = newListOf(selectedDepictions);
public void onDepictItemClicked(DepictedItem depictedItem) {
if (depictedItem.isSelected()) {
selectedDepictions.add(depictedItem);
} else {
selectedDepictions.remove(depictedItem);
}
}
@NotNull
private <T> ArrayList<T> newListOf(final List<T> items) {
private <T> List<T> newListOf(final List<T> items) {
return items != null ? new ArrayList<>(items) : new ArrayList<>();
}
@ -222,13 +197,16 @@ public class UploadModel {
items.get(uploadItemIndex).setGpsCoords(imageCoordinates);
}
public List<DepictedItem> getSelectedDepictions() {
return selectedDepictions;
}
@SuppressWarnings("WeakerAccess")
public static class UploadItem {
private final Uri originalContentUri;
private final Uri mediaUri;
private final String mimeType;
private final String source;
private ImageCoordinates gpsCoords;
private List<UploadMediaDetail> uploadMediaDetails;
private final Place place;
@ -237,7 +215,8 @@ public class UploadModel {
private final BehaviorSubject<Integer> imageQuality;
@SuppressLint("CheckResult")
UploadItem(final Uri originalContentUri,
final Uri mediaUri, final String mimeType, final String source, final ImageCoordinates gpsCoords,
final Uri mediaUri, final String mimeType,
final ImageCoordinates gpsCoords,
final Place place,
final long createdTimestamp,
final String createdTimestampSource) {
@ -247,7 +226,6 @@ public class UploadModel {
this.place = place;
this.mediaUri = mediaUri;
this.mimeType = mimeType;
this.source = source;
this.gpsCoords = gpsCoords;
this.createdTimestamp = createdTimestamp;
imageQuality = BehaviorSubject.createDefault(ImageUtils.IMAGE_WAIT);
@ -257,10 +235,6 @@ public class UploadModel {
return createdTimestampSource;
}
public String getSource() {
return source;
}
public ImageCoordinates getGpsCoords() {
return gpsCoords;
}
@ -323,5 +297,8 @@ public class UploadModel {
this.gpsCoords = gpsCoords;
}
public String getMimeType() {
return mimeType;
}
}
}

View file

@ -2,4 +2,16 @@ package fr.free.nrw.commons.upload
import org.wikipedia.gallery.ImageInfo
class UploadResult(val result: String, val filekey: String, val filename: String, val sessionkey: String, val imageinfo: ImageInfo)
private const val RESULT_SUCCESS = "Success"
data class UploadResult(
val result: String,
val filekey: String,
val filename: String,
val sessionkey: String,
val imageinfo: ImageInfo
) {
fun isSuccessful(): Boolean = result == RESULT_SUCCESS
fun createCanonicalFileName() = "File:$filename"
}

View file

@ -7,21 +7,8 @@ import android.content.Intent;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.inject.Named;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.HandlerService;
@ -38,6 +25,15 @@ import io.reactivex.Observable;
import io.reactivex.Scheduler;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber;
public class UploadService extends HandlerService<Contribution> {
@ -47,7 +43,6 @@ public class UploadService extends HandlerService<Contribution> {
public static final int ACTION_UPLOAD_FILE = 1;
public static final String ACTION_START_SERVICE = EXTRA_PREFIX + ".upload";
public static final String EXTRA_SOURCE = EXTRA_PREFIX + ".source";
public static final String EXTRA_FILES = EXTRA_PREFIX + ".files";
@Inject WikidataEditService wikidataEditService;
@Inject SessionManager sessionManager;
@ -151,7 +146,6 @@ public class UploadService extends HandlerService<Contribution> {
@Override
public void queue(int what, Contribution contribution) {
Timber.d("Upload service queue has contribution with wiki data entity id as %s", contribution.getWikiDataEntityId());
switch (what) {
case ACTION_UPLOAD_FILE:
@ -168,7 +162,7 @@ public class UploadService extends HandlerService<Contribution> {
.subscribeOn(ioThreadScheduler)
.observeOn(mainThreadScheduler)
.subscribe(aLong->{
contribution._id = aLong;
contribution.set_id(aLong);
UploadService.super.queue(what, contribution);
}, Throwable::printStackTrace));
break;
@ -251,12 +245,7 @@ public class UploadService extends HandlerService<Contribution> {
Timber.d("Stash upload response 1 is %s", uploadStash.toString());
String resultStatus = uploadStash.getResult();
if (!resultStatus.equals("Success")) {
Timber.d("Contribution upload failed. Wikidata entity won't be edited");
showFailedNotification(contribution);
return Observable.never();
} else {
if (uploadStash.isSuccessful()) {
Timber.d("making sure of uniqueness of name: %s", filename);
String uniqueFilename = findUniqueFilename(filename);
unfinishedUploads.add(uniqueFilename);
@ -265,6 +254,10 @@ public class UploadService extends HandlerService<Contribution> {
contribution,
uniqueFilename,
uploadStash.getFilekey());
} else {
Timber.d("Contribution upload failed. Wikidata entity won't be edited");
showFailedNotification(contribution);
return Observable.never();
}
})
.subscribe(
@ -282,32 +275,27 @@ public class UploadService extends HandlerService<Contribution> {
notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS);
String resultStatus = uploadResult.getResult();
if (!resultStatus.equals("Success")) {
if (uploadResult.isSuccessful()) {
onSuccessfulUpload(contribution, uploadResult);
} else {
Timber.d("Contribution upload failed. Wikidata entity won't be edited");
showFailedNotification(contribution);
} else {
String canonicalFilename = "File:" + uploadResult.getFilename();
final String wikiDataEntityId = contribution.getWikiDataEntityId();
Timber.d("Contribution upload success. Initiating Wikidata edit for entity id %s",
wikiDataEntityId);
// to perform upload of depictions we pass on depiction entityId of the selected depictions to the wikidataEditService
final String p18Value = contribution.getP18Value();
final String wikiItemName = contribution.getWikiItemName();
if (contribution.getDepictionsEntityIds() != null) {
for (String depictionEntityId : contribution.getDepictionsEntityIds()) {
wikidataEditService.createClaimWithLogging(depictionEntityId,
wikiItemName, canonicalFilename, p18Value);
}
}
Timber.d("Contribution upload success. Initiating Wikidata edit for"
+ " entity id %s if necessary (if P18 is null). P18 value is %s",
wikiDataEntityId, p18Value);
wikidataEditService.createClaimWithLogging(
wikiDataEntityId, wikiItemName, canonicalFilename,p18Value);
wikidataEditService.createLabelforWikidataEntity(canonicalFilename, contribution.getCaptions());
contribution.setFilename(canonicalFilename);
private void onSuccessfulUpload(Contribution contribution, UploadResult uploadResult)
throws ParseException {
compositeDisposable
.add(wikidataEditService.addDepictionsAndCaptions(uploadResult, contribution));
WikidataPlace wikidataPlace = contribution.getWikidataPlace();
if (wikidataPlace != null && wikidataPlace.getImageValue() == null) {
wikidataEditService.createImageClaim(wikidataPlace, uploadResult);
}
saveCompletedContribution(contribution, uploadResult);
}
private void saveCompletedContribution(Contribution contribution, UploadResult uploadResult) throws ParseException {
contribution.setFilename(uploadResult.createCanonicalFileName());
contribution.setImageUrl(uploadResult.getImageinfo().getOriginalUrl());
contribution.setState(Contribution.STATE_COMPLETED);
contribution.setDateUploaded(CommonsDateUtil.getIso8601DateFormatTimestamp()
@ -318,7 +306,6 @@ public class UploadService extends HandlerService<Contribution> {
.observeOn(mainThreadScheduler)
.subscribe());
}
}
@SuppressLint("StringFormatInvalid")
@SuppressWarnings("deprecation")

View file

@ -1,11 +1,11 @@
package fr.free.nrw.commons.upload;
import androidx.annotation.NonNull;
import static org.wikipedia.dataclient.Service.MW_API_PREFIX;
import androidx.annotation.NonNull;
import io.reactivex.Observable;
import org.wikipedia.dataclient.mwapi.MwPostResponse;
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
import io.reactivex.Observable;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
@ -13,8 +13,6 @@ import retrofit2.http.Headers;
import retrofit2.http.POST;
import retrofit2.http.Query;
import static org.wikipedia.dataclient.Service.MW_API_PREFIX;
/**
* Retrofit calls for managing responses network calls of entity ids required for uploading depictions
*/
@ -31,4 +29,18 @@ public interface WikiBaseInterface {
@GET(MW_API_PREFIX + "action=query&prop=info")
Observable<MwQueryResponse> getFileEntityId(@Query("titles") String fileName);
/**
* Upload Captions for the image when upload is successful
*
* @param fileEntityId enityId for the uploaded file
* @param editToken editToken for the file
* @param captionValue value of the caption to be uploaded
*/
@FormUrlEncoded
@POST(MW_API_PREFIX + "action=wbsetlabel")
Observable<MwPostResponse> addLabelstoWikidata(@Field("id") String fileEntityId,
@Field("token") String editToken,
@Field("language") String language,
@Field("value") String captionValue);
}

View file

@ -0,0 +1,6 @@
package fr.free.nrw.commons.upload
interface WikidataItem {
val id:String
val name:String
}

View file

@ -0,0 +1,21 @@
package fr.free.nrw.commons.upload
import android.os.Parcelable
import fr.free.nrw.commons.nearby.Place
import kotlinx.android.parcel.Parcelize
@Parcelize
internal data class WikidataPlace(override val id: String, override val name: String, val imageValue: String?) :
WikidataItem,Parcelable {
constructor(place: Place) : this(
place.wikiDataEntityId!!,
place.name,
place.pic.takeIf { it.isNotBlank() })
companion object {
@JvmStatic
fun from(place: Place?): WikidataPlace? {
return place?.let { WikidataPlace(it) }
}
}
}

View file

@ -126,7 +126,6 @@ public class DepictsPresenter implements DepictsContract.UserActionListener {
public void verifyDepictions() {
List<DepictedItem> selectedDepictions = repository.getSelectedDepictions();
if (selectedDepictions != null && !selectedDepictions.isEmpty()) {
repository.setSelectedDepictions(repository.getDepictionsEntityIdList());
view.goToNextScreen();
} else {
view.noDepictionSelected();

View file

@ -1,31 +0,0 @@
package fr.free.nrw.commons.upload.mediaDetails;
import org.wikipedia.dataclient.mwapi.MwPostResponse;
import java.util.Map;
import io.reactivex.Observable;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST;
import static org.wikipedia.dataclient.Service.MW_API_PREFIX;
public interface CaptionInterface {
/**
* Upload Captions for the image when upload is successful
*
* @param FileEntityId enityId for the uploaded file
* @param editToken editToken for the file
* @param captionValue value of the caption to be uploaded
* @param caption additional data associated with caption
*/
@FormUrlEncoded
@POST(MW_API_PREFIX + "action=wbsetlabel&language=en")
Observable<MwPostResponse> addLabelstoWikidata(@Field("id") String FileEntityId,
@Field("token") String editToken,
@Field("value") String captionValue,
@Field("data") Map<String, String> caption);
}

View file

@ -81,7 +81,6 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
JsonKvStore defaultKvStore;
private UploadableFile uploadableFile;
private String source;
private Place place;
private boolean isExpanded = true;
@ -97,9 +96,8 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
super.onCreate(savedInstanceState);
}
public void setImageTobeUploaded(UploadableFile uploadableFile, String source, Place place) {
public void setImageTobeUploaded(UploadableFile uploadableFile, Place place) {
this.uploadableFile = uploadableFile;
this.source = source;
this.place = place;
}
@ -122,7 +120,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
callback.getTotalNumberOfSteps()));
initRecyclerView();
initPresenter();
presenter.receiveImage(uploadableFile, source, place);
presenter.receiveImage(uploadableFile, place);
if (callback.getIndexInViewFlipper(this) == 0) {
btnPrevious.setEnabled(false);

View file

@ -1,15 +1,13 @@
package fr.free.nrw.commons.upload.mediaDetails;
import fr.free.nrw.commons.upload.ImageCoordinates;
import java.util.List;
import fr.free.nrw.commons.BasePresenter;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.filepicker.UploadableFile;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.upload.UploadMediaDetail;
import fr.free.nrw.commons.upload.ImageCoordinates;
import fr.free.nrw.commons.upload.SimilarImageInterface;
import fr.free.nrw.commons.upload.UploadMediaDetail;
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
import java.util.List;
/**
* The contract with with UploadMediaDetails and its presenter would talk to each other
@ -41,8 +39,7 @@ public interface UploadMediaDetailsContract {
interface UserActionListener extends BasePresenter<View> {
void receiveImage(UploadableFile uploadableFile, @Contribution.FileSource String source,
Place place);
void receiveImage(UploadableFile uploadableFile, Place place);
void verifyImageQuality(UploadItem uploadItem);

View file

@ -64,16 +64,14 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
/**
* Receives the corresponding uploadable file, processes it and return the view with and uplaod item
*
* @param uploadableFile
* @param source
* @param place
*/
@Override
public void receiveImage(UploadableFile uploadableFile, String source, Place place) {
public void receiveImage(UploadableFile uploadableFile, Place place) {
view.showProgress(true);
Disposable uploadItemDisposable = repository
.preProcessImage(uploadableFile, place, source, this)
.preProcessImage(uploadableFile, place, this)
.subscribeOn(ioScheduler)
.observeOn(mainThreadScheduler)
.subscribe(uploadItem ->

View file

@ -2,7 +2,7 @@ package fr.free.nrw.commons.upload.structure.depictions
import fr.free.nrw.commons.upload.depicts.DepictsInterface
import io.reactivex.Observable
import java.util.Locale
import java.util.*
import javax.inject.Inject
/**
@ -14,24 +14,6 @@ class DepictModel @Inject constructor(private val depictsInterface: DepictsInter
private const val SEARCH_DEPICTS_LIMIT = 25
}
val selectedDepictions = mutableListOf<DepictedItem>()
fun onDepictItemClicked(depictedItem: DepictedItem) {
if (depictedItem.isSelected) {
selectDepictItem(depictedItem)
} else {
unselectDepiction(depictedItem)
}
}
private fun unselectDepiction(depictedItem: DepictedItem) {
selectedDepictions.remove(depictedItem)
}
private fun selectDepictItem(depictedItem: DepictedItem) {
selectedDepictions.add(depictedItem)
}
/**
* Search for depictions
*/
@ -44,5 +26,4 @@ class DepictModel @Inject constructor(private val depictsInterface: DepictsInter
.map(::DepictedItem)
}
fun depictionsEntityIdList() = selectedDepictions.map { it.entityId }
}

View file

@ -1,17 +1,18 @@
package fr.free.nrw.commons.upload.structure.depictions
import fr.free.nrw.commons.upload.WikidataItem
import fr.free.nrw.commons.wikidata.model.DepictSearchItem
/**
* Model class for Depicted Item in Upload and Explore
*/
data class DepictedItem constructor(
val depictsLabel: String?,
override val name: String,
val description: String?,
var imageUrl: String,
var isSelected: Boolean,
val entityId: String
) {
override val id: String
):WikidataItem {
constructor(depictSearchItem: DepictSearchItem) : this(
depictSearchItem.label,
depictSearchItem.description,
@ -24,12 +25,12 @@ data class DepictedItem constructor(
override fun equals(o: Any?) = when {
this === o -> true
o is DepictedItem -> depictsLabel == o.depictsLabel
o is DepictedItem -> name == o.name
else -> false
}
override fun hashCode(): Int {
return depictsLabel?.hashCode() ?: 0
return name?.hashCode() ?: 0
}
}

View file

@ -50,7 +50,7 @@ public class DepictionRenderer extends Renderer<DepictedItem> {
public void render() {
DepictedItem item = getContent();
checkedView.setChecked(item.isSelected());
depictsLabel.setText(item.getDepictsLabel());
depictsLabel.setText(item.getName());
description.setText(item.getDescription());
}
}

View file

@ -1,16 +1,17 @@
package fr.free.nrw.commons.wikidata;
import org.wikipedia.csrf.CsrfTokenClient;
import static fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX;
import static fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF;
import fr.free.nrw.commons.upload.UploadResult;
import fr.free.nrw.commons.upload.WikiBaseInterface;
import io.reactivex.Observable;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import fr.free.nrw.commons.upload.WikiBaseInterface;
import fr.free.nrw.commons.utils.ConfigUtils;
import io.reactivex.Observable;
import static fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF;
import org.wikipedia.csrf.CsrfTokenClient;
import org.wikipedia.dataclient.mwapi.MwPostResponse;
import timber.log.Timber;
/**
* Wikibase Client for calling WikiBase APIs
@ -29,16 +30,32 @@ public class WikiBaseClient {
}
public Observable<Boolean> postEditEntity(String fileEntityId, String data) {
try {
return wikiBaseInterface.postEditEntity(fileEntityId, csrfTokenClient.getTokenBlocking(), data)
.map(response -> (response.getSuccessVal() == 1));
} catch (Throwable throwable) {
return Observable.just(false);
}
return csrfToken()
.switchMap(editToken -> wikiBaseInterface.postEditEntity(fileEntityId, editToken, data)
.map(response -> (response.getSuccessVal() == 1)));
}
public Observable<Long> getFileEntityId(String fileName) {
return wikiBaseInterface.getFileEntityId(fileName)
public Observable<Long> getFileEntityId(UploadResult uploadResult) {
return wikiBaseInterface.getFileEntityId(uploadResult.createCanonicalFileName())
.map(response -> (long) (response.query().pages().get(0).pageId()));
}
public Observable<MwPostResponse> addLabelstoWikidata(long fileEntityId,
String languageCode, String captionValue) {
return csrfToken()
.switchMap(editToken -> wikiBaseInterface
.addLabelstoWikidata(PAGE_ID_PREFIX + fileEntityId, editToken, languageCode, captionValue));
}
private Observable<String> csrfToken() {
return Observable.fromCallable(() -> {
try {
return csrfTokenClient.getTokenBlocking();
} catch (Throwable throwable) {
Timber.e(throwable);
return "";
}
});
}
}

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.wikidata;
import fr.free.nrw.commons.upload.WikidataItem;
import org.jetbrains.annotations.NotNull;
import javax.inject.Inject;
@ -24,15 +25,16 @@ public class WikidataClient {
/**
* Create wikidata claim to add P18 value
* @param entityId wikidata entity ID
* @param entity wikidata entity ID
* @param value value of the P18 edit
* @return revisionID of the edit
*/
Observable<Long> createClaim(String entityId, String value) {
Observable<Long> createImageClaim(WikidataItem entity, String value) {
return getCsrfToken()
.flatMap(csrfToken -> wikidataInterface.postCreateClaim(toRequestBody(entityId),
.flatMap(csrfToken -> wikidataInterface.postCreateClaim(
toRequestBody(entity.getId()),
toRequestBody("value"),
toRequestBody("P18"),
toRequestBody(WikidataProperties.IMAGE.getPropertyName()),
toRequestBody(value),
toRequestBody("en"),
toRequestBody(csrfToken)))

View file

@ -4,27 +4,28 @@ import static fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_I
import android.annotation.SuppressLint;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.upload.mediaDetails.CaptionInterface;
import fr.free.nrw.commons.upload.UploadResult;
import fr.free.nrw.commons.upload.WikidataItem;
import fr.free.nrw.commons.upload.WikidataPlace;
import fr.free.nrw.commons.utils.ConfigUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Callable;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.wikipedia.csrf.CsrfTokenClient;
import org.jetbrains.annotations.NotNull;
import org.wikipedia.dataclient.mwapi.MwPostResponse;
import timber.log.Timber;
/**
@ -35,129 +36,55 @@ import timber.log.Timber;
@Singleton
public class WikidataEditService {
private final static String COMMONS_APP_TAG = "wikimedia-commons-app";
private final static String COMMONS_APP_EDIT_REASON = "Add tag for edits made using Android Commons app";
private static final String COMMONS_APP_TAG = "wikimedia-commons-app";
private static final String COMMONS_APP_EDIT_REASON = "Add tag for edits made using Android Commons app";
private final Context context;
private final WikidataEditListener wikidataEditListener;
private final JsonKvStore directKvStore;
private final CaptionInterface captionInterface;
private final WikiBaseClient wikiBaseClient;
private final WikidataClient wikidataClient;
private final CsrfTokenClient csrfTokenClient;
@Inject
public WikidataEditService(final Context context,
final WikidataEditListener wikidataEditListener,
@Named("default_preferences") final JsonKvStore directKvStore,
final WikiBaseClient wikiBaseClient,
final CaptionInterface captionInterface,
final WikidataClient wikidataClient,
@Named("commons-csrf") final CsrfTokenClient csrfTokenClient) {
final WikidataClient wikidataClient) {
this.context = context;
this.wikidataEditListener = wikidataEditListener;
this.directKvStore = directKvStore;
this.captionInterface = captionInterface;
this.wikiBaseClient = wikiBaseClient;
this.wikidataClient = wikidataClient;
this.csrfTokenClient = csrfTokenClient;
}
/**
* Create a P18 claim and log the edit with custom tag
*
* @param wikidataEntityId a unique id of each Wikidata items
* @param fileName name of the file we will upload
* @param p18Value pic attribute of Wikidata item
*/
public void createClaimWithLogging(String wikidataEntityId, String wikiItemName, String fileName, String p18Value) {
if (wikidataEntityId == null) {
Timber.d("Skipping creation of claim as Wikidata entity ID is null");
return;
}
if (fileName == null) {
Timber.d("Skipping creation of claim as fileName entity ID is null");
return;
}
if (!(directKvStore.getBoolean("Picture_Has_Correct_Location", true))) {
Timber.d("Image location and nearby place location mismatched, so Wikidata item won't be edited");
return;
}
if (p18Value != null && !p18Value.trim().isEmpty()) {
Timber.d("Skipping creation of claim as p18Value is not empty, we won't override existing image");
return;
}
editWikidataProperty(wikidataEntityId, wikiItemName, fileName);;
editWikiBaseDepictsProperty(wikidataEntityId, fileName);
}
/**
* Edits the wikidata entity by adding the P18 property to it.
* Adding the P18 edit requires calling the wikidata API to create a claim against the entity
*
* @param wikidataEntityId
* @param fileName
*/
@SuppressLint("CheckResult")
private void editWikidataProperty(String wikidataEntityId, String wikiItemName, String fileName) {
Timber.d("Upload successful with wiki data entity id as %s", wikidataEntityId);
Timber.d("Attempting to edit Wikidata property %s", wikidataEntityId);
final String propertyValue = getFileName(fileName);
Timber.d("Entity id is %s and property value is %s", wikidataEntityId, propertyValue);
wikidataClient.createClaim(wikidataEntityId, propertyValue)
.flatMap(revisionId -> {
if (revisionId != -1) {
return wikidataClient.addEditTag(revisionId, COMMONS_APP_TAG, COMMONS_APP_EDIT_REASON);
}
throw new RuntimeException("Unable to edit wikidata item");
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(revisionId -> handleClaimResult(wikidataEntityId, wikiItemName, String.valueOf(revisionId)), throwable -> {
Timber.e(throwable, "Error occurred while making claim");
ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure));
});
}
/**
* Edits the wikibase entity by adding DEPICTS property.
* Adding DEPICTS property requires call to the wikibase API to set tag against the entity.
*
* @param wikidataEntityId
* @param fileName
*/
@SuppressLint("CheckResult")
private void editWikiBaseDepictsProperty(final String wikidataEntityId, final String fileName) {
wikiBaseClient.getFileEntityId(fileName)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(fileEntityId -> {
if (fileEntityId != null) {
Timber.d("EntityId for image was received successfully: %s", fileEntityId);
addDepictsProperty(wikidataEntityId, fileEntityId.toString());
private Observable<Boolean> addDepictsProperty(final String fileEntityId,
final WikidataItem depictedItem) {
// Wikipedia:Sandbox (Q10)
final String data = depictionJson(ConfigUtils.isBetaFlavour() ? "Q10" : depictedItem.getId());
return wikiBaseClient.postEditEntity(PAGE_ID_PREFIX + fileEntityId, data)
.doOnNext(success -> {
if (success) {
Timber.d("DEPICTS property was set successfully for %s", fileEntityId);
} else {
Timber.d("Error acquiring EntityId for image: %s", fileName);
Timber.d("Unable to set DEPICTS property for %s", fileEntityId);
}
}, throwable -> {
Timber.e(throwable, "Error occurred while getting EntityID to set DEPICTS property");
ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure));
});
}
@SuppressLint("CheckResult")
private void addDepictsProperty(String entityId, final String fileEntityId) {
if (ConfigUtils.isBetaFlavour()) {
entityId = "Q10"; // Wikipedia:Sandbox (Q10)
})
.doOnError( throwable -> {
Timber.e(throwable, "Error occurred while setting DEPICTS property");
ViewUtil.showLongToast(context, throwable.toString());
})
.subscribeOn(Schedulers.io());
}
@NotNull
private String depictionJson(final String entityId) {
final JsonObject value = new JsonObject();
value.addProperty("entity-type", "item");
value.addProperty("numeric-id", entityId.replace("Q", ""));
@ -169,7 +96,7 @@ public class WikidataEditService {
final JsonObject mainSnak = new JsonObject();
mainSnak.addProperty("snaktype", "value");
mainSnak.addProperty("property", BuildConfig.DEPICTS_PROPERTY);
mainSnak.addProperty("property", WikidataProperties.DEPICTS.getPropertyName());
mainSnak.add("datavalue", dataValue);
final JsonObject claim = new JsonObject();
@ -183,131 +110,119 @@ public class WikidataEditService {
final JsonObject jsonData = new JsonObject();
jsonData.add("claims", claims);
final String data = jsonData.toString();
Observable.defer((Callable<ObservableSource<Boolean>>) () ->
wikiBaseClient.postEditEntity(PAGE_ID_PREFIX + fileEntityId, data))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(success -> {
if (success)
Timber.d("DEPICTS property was set successfully for %s", fileEntityId);
else
Timber.d("Unable to set DEPICTS property for %s", fileEntityId);
},
throwable -> {
Timber.e(throwable, "Error occurred while setting DEPICTS property");
ViewUtil.showLongToast(context, throwable.toString());
});
return jsonData.toString();
}
private void handleClaimResult(String wikidataEntityId, String wikiItemName, String revisionId) {
if (revisionId != null) {
if (wikidataEditListener != null) {
wikidataEditListener.onSuccessfulWikidataEdit();
}
showSuccessToast(wikiItemName);
} else {
Timber.d("Unable to make wiki data edit for entity %s", wikidataEntityId);
ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure));
}
}
/**
* Show a success toast when the edit is made successfully
*/
private void showSuccessToast(String wikiItemName) {
String successStringTemplate = context.getString(R.string.successful_wikidata_edit);
String successMessage = String.format(Locale.getDefault(), successStringTemplate, wikiItemName);
private void showSuccessToast(final String wikiItemName) {
final String successStringTemplate = context.getString(R.string.successful_wikidata_edit);
final String successMessage = String.format(Locale.getDefault(), successStringTemplate, wikiItemName);
ViewUtil.showLongToast(context, successMessage);
}
/**
* Formats and returns the filename as accepted by the wiki base API
* https://www.mediawiki.org/wiki/Wikibase/API#wbcreateclaim
*
* @param fileName
* @return
*/
private String getFileName(String fileName) {
fileName = String.format("\"%s\"", fileName.replace("File:", ""));
Timber.d("Wikidata property name is %s", fileName);
return fileName;
}
/**
* Adding captions as labels after image is successfully uploaded
*/
@SuppressLint("CheckResult")
public void createLabelforWikidataEntity(final String fileName,
final Map<String, String> captions) {
Observable.fromCallable(() -> wikiBaseClient.getFileEntityId(fileName))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(fileEntityId -> {
if (fileEntityId != null) {
for (final Map.Entry<String, String> entry : captions.entrySet()) {
final Map<String, String> caption = new HashMap<>();
caption.put(entry.getKey(), entry.getValue());
try {
wikidataAddLabels(fileEntityId.toString(), caption);
} catch (final Throwable throwable) {
throwable.printStackTrace();
}
}
} else {
Timber.d("Error acquiring EntityId for image");
}
}, throwable -> {
Timber.e(throwable, "Error occurred while getting EntityID for the file");
ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure));
});
}
/**
* Adds label to Wikidata using the fileEntityId and the edit token, obtained from csrfTokenClient
*
* @param fileEntityId
* @param caption
* @return
*/
@SuppressLint("CheckResult")
private void wikidataAddLabels(final String fileEntityId, final Map<String, String> caption) {
Observable.fromCallable(() -> {
try {
return csrfTokenClient.getTokenBlocking();
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
private Observable<Boolean> addCaption(final long fileEntityId, final String languageCode,
final String captionValue) {
return wikiBaseClient.addLabelstoWikidata(fileEntityId, languageCode, captionValue)
.doOnNext(mwPostResponse -> onAddCaptionResponse(fileEntityId, mwPostResponse) )
.doOnError(throwable -> {
Timber.e(throwable, "Error occurred while setting Captions");
ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure));
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(editToken -> {
if (editToken != null) {
Observable.fromCallable(() -> captionInterface.addLabelstoWikidata(fileEntityId, editToken, caption.get(0), caption))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(revisionId ->
{
if (revisionId != null) {
Timber.d("Caption successfully set, revision id = %s", revisionId);
.map(mwPostResponse -> mwPostResponse != null);
}
private void onAddCaptionResponse(Long fileEntityId, MwPostResponse response) {
if (response != null) {
Timber.d("Caption successfully set, revision id = %s", response);
} else {
Timber.d("Error occurred while setting Captions, fileEntityId = %s", fileEntityId);
}
},
throwable -> {
Timber.e(throwable, "Error occurred while setting Captions");
ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure));
});
}else {
Timber.d("Error acquiring EntityId for image");
}
}, throwable -> {
Timber.e(throwable, "Error occurred while getting EntityID for the File");
public void createImageClaim(@Nullable final WikidataPlace wikidataPlace, final UploadResult imageUpload) {
if (!(directKvStore.getBoolean("Picture_Has_Correct_Location", true))) {
Timber.d("Image location and nearby place location mismatched, so Wikidata item won't be edited");
return;
}
editWikidataImageProperty(wikidataPlace, imageUpload);
}
@SuppressLint("CheckResult")
private void editWikidataImageProperty(final WikidataItem wikidataItem, final UploadResult imageUpload) {
wikidataClient.createImageClaim(wikidataItem, String.format("\"%s\"", imageUpload.getFilename()))
.flatMap(revisionId -> {
if (revisionId != -1) {
return wikidataClient.addEditTag(revisionId, COMMONS_APP_TAG, COMMONS_APP_EDIT_REASON);
}
throw new RuntimeException("Unable to edit wikidata item");
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(revisionId -> handleImageClaimResult(wikidataItem, String.valueOf(revisionId)), throwable -> {
Timber.e(throwable, "Error occurred while making claim");
ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure));
});
}
private void handleImageClaimResult(final WikidataItem wikidataItem, final String revisionId) {
if (revisionId != null) {
if (wikidataEditListener != null) {
wikidataEditListener.onSuccessfulWikidataEdit();
}
showSuccessToast(wikidataItem.getName());
} else {
Timber.d("Unable to make wiki data edit for entity %s", wikidataItem);
ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure));
}
}
public Disposable addDepictionsAndCaptions(UploadResult uploadResult, Contribution contribution) {
return wikiBaseClient.getFileEntityId(uploadResult)
.doOnError(throwable -> {
Timber.e(throwable, "Error occurred while getting EntityID to set DEPICTS property");
ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure));
})
.subscribeOn(Schedulers.io())
.switchMap(fileEntityId -> {
if (fileEntityId != null) {
Timber.d("EntityId for image was received successfully: %s", fileEntityId);
return Observable.concat(
depictionEdits(contribution, fileEntityId),
captionEdits(contribution, fileEntityId)
);
} else {
Timber.d("Error acquiring EntityId for image: %s", uploadResult);
return Observable.empty();
}
}
).subscribe(
success -> Timber.d("edit response: %s", success),
throwable -> Timber.e(throwable, "posting edits failed")
);
}
private Observable<Boolean> captionEdits(Contribution contribution, Long fileEntityId) {
return Observable.fromIterable(contribution.getCaptions().entrySet())
.concatMap(entry -> addCaption(fileEntityId, entry.getKey(), entry.getValue()));
}
private Observable<Boolean> depictionEdits(Contribution contribution, Long fileEntityId) {
final ArrayList<WikidataItem> depictedItems = new ArrayList<>(contribution.getDepictedItems());
final WikidataPlace wikidataPlace = contribution.getWikidataPlace();
if (wikidataPlace != null) {
depictedItems.add(wikidataPlace);
}
return Observable.fromIterable(depictedItems)
.concatMap( wikidataItem -> addDepictsProperty(fileEntityId.toString(), wikidataItem));
}
}

View file

@ -0,0 +1,18 @@
package fr.free.nrw.commons.wikidata;
import fr.free.nrw.commons.BuildConfig;
enum WikidataProperties {
IMAGE("P18"),
DEPICTS(BuildConfig.DEPICTS_PROPERTY);
private final String propertyName;
WikidataProperties(final String propertyName) {
this.propertyName = propertyName;
}
public String getPropertyName() {
return propertyName;
}
}

View file

@ -2,11 +2,26 @@ package fr.free.nrw.commons.wikidata.model
/**
* Model class for Depiction item returned from API after calling searchForDepicts
*/
"search": [
{
"repository": "local",
"id": "Q23444",
"concepturi": "http://www.wikidata.org/entity/Q23444",
"title": "Q23444",
"pageid": 26835,
"url": "//www.wikidata.org/wiki/Q23444",
"label": "white",
"description": "color",
"match": {
"type": "label",
"language": "en",
"text": "white"
}
}*/
class DepictSearchItem(
val id: String,
val pageid: String,
val url: String,
val label: String?,
val label: String,
val description: String?
)

View file

@ -2,22 +2,19 @@ package fr.free.nrw.commons.delete
import android.content.Context
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.actions.PageEditClient
import fr.free.nrw.commons.notification.NotificationHelper
import fr.free.nrw.commons.utils.ViewUtilWrapper
import io.reactivex.Observable
import org.junit.Assert.*
import org.junit.Assert.assertNotNull
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.MockitoAnnotations
import javax.inject.Inject
import javax.inject.Named
/**
* Tests for delete helper
@ -25,23 +22,15 @@ import javax.inject.Named
class DeleteHelperTest {
@Mock
@field:[Inject Named("commons-page-edit")]
internal var pageEditClient: PageEditClient? = null
internal lateinit var pageEditClient: PageEditClient
@Mock
internal var context: Context? = null
internal lateinit var context: Context
@Mock
internal var notificationHelper: NotificationHelper? = null
internal lateinit var media: Media
@Mock
internal var viewUtil: ViewUtilWrapper? = null
@Mock
internal var media: Media? = null
@InjectMocks
var deleteHelper: DeleteHelper? = null
lateinit var deleteHelper: DeleteHelper
/**
* Init mocks for test
@ -49,6 +38,7 @@ class DeleteHelperTest {
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
deleteHelper = DeleteHelper(mock(), pageEditClient, mock(), "")
}
/**
@ -56,23 +46,23 @@ class DeleteHelperTest {
*/
@Test
fun makeDeletion() {
`when`(pageEditClient?.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
whenever(pageEditClient.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
.thenReturn(Observable.just(true))
`when`(pageEditClient?.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
whenever(pageEditClient.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
.thenReturn(Observable.just(true))
`when`(pageEditClient?.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
whenever(pageEditClient.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
.thenReturn(Observable.just(true))
`when`(media?.displayTitle).thenReturn("Test file")
media?.filename="Test file.jpg"
whenever(media.displayTitle).thenReturn("Test file")
val creatorName = "Creator"
`when`(media?.getCreator()).thenReturn("$creatorName (page does not exist)")
whenever(media.creator).thenReturn("$creatorName (page does not exist)")
whenever(media.filename).thenReturn("Test file.jpg")
val makeDeletion = deleteHelper?.makeDeletion(context, media, "Test reason")?.blockingGet()
val makeDeletion = deleteHelper.makeDeletion(context, media, "Test reason")?.blockingGet()
assertNotNull(makeDeletion)
assertTrue(makeDeletion!!)
verify(pageEditClient)?.appendEdit(eq("User_Talk:$creatorName"), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())
verify(pageEditClient).appendEdit(eq("User_Talk:$creatorName"), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())
}
/**
@ -80,63 +70,63 @@ class DeleteHelperTest {
*/
@Test(expected = RuntimeException::class)
fun makeDeletionForPrependEditFailure() {
`when`(pageEditClient?.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
whenever(pageEditClient.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
.thenReturn(Observable.just(false))
`when`(pageEditClient?.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
whenever(pageEditClient.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
.thenReturn(Observable.just(true))
`when`(pageEditClient?.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
whenever(pageEditClient.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
.thenReturn(Observable.just(true))
`when`(media?.displayTitle).thenReturn("Test file")
`when`(media?.filename).thenReturn("Test file.jpg")
`when`(media?.creator).thenReturn("Creator (page does not exist)")
whenever(media.displayTitle).thenReturn("Test file")
whenever(media.filename).thenReturn("Test file.jpg")
whenever(media.creator).thenReturn("Creator (page does not exist)")
deleteHelper?.makeDeletion(context, media, "Test reason")?.blockingGet()
deleteHelper.makeDeletion(context, media, "Test reason")?.blockingGet()
}
@Test(expected = RuntimeException::class)
fun makeDeletionForEditFailure() {
`when`(pageEditClient?.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
whenever(pageEditClient.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
.thenReturn(Observable.just(true))
`when`(pageEditClient?.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
whenever(pageEditClient.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
.thenReturn(Observable.just(true))
`when`(pageEditClient?.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
whenever(pageEditClient.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
.thenReturn(Observable.just(false))
`when`(media?.displayTitle).thenReturn("Test file")
`when`(media?.filename).thenReturn("Test file.jpg")
`when`(media?.creator).thenReturn("Creator (page does not exist)")
whenever(media.displayTitle).thenReturn("Test file")
whenever(media.filename).thenReturn("Test file.jpg")
whenever(media.creator).thenReturn("Creator (page does not exist)")
deleteHelper?.makeDeletion(context, media, "Test reason")?.blockingGet()
deleteHelper.makeDeletion(context, media, "Test reason")?.blockingGet()
}
@Test(expected = RuntimeException::class)
fun makeDeletionForAppendEditFailure() {
`when`(pageEditClient?.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
whenever(pageEditClient.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
.thenReturn(Observable.just(true))
`when`(pageEditClient?.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
whenever(pageEditClient.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
.thenReturn(Observable.just(false))
`when`(pageEditClient?.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
whenever(pageEditClient.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
.thenReturn(Observable.just(true))
`when`(media?.displayTitle).thenReturn("Test file")
`when`(media?.filename).thenReturn("Test file.jpg")
`when`(media?.creator).thenReturn("Creator (page does not exist)")
whenever(media.displayTitle).thenReturn("Test file")
whenever(media.filename).thenReturn("Test file.jpg")
whenever(media.creator).thenReturn("Creator (page does not exist)")
deleteHelper?.makeDeletion(context, media, "Test reason")?.blockingGet()
deleteHelper.makeDeletion(context, media, "Test reason")?.blockingGet()
}
@Test(expected = RuntimeException::class)
fun makeDeletionForEmptyCreatorName() {
`when`(pageEditClient?.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
whenever(pageEditClient.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
.thenReturn(Observable.just(true))
`when`(pageEditClient?.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
whenever(pageEditClient.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
.thenReturn(Observable.just(true))
`when`(pageEditClient?.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
whenever(pageEditClient.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
.thenReturn(Observable.just(true))
`when`(media?.displayTitle).thenReturn("Test file")
media?.filename="Test file.jpg"
whenever(media.displayTitle).thenReturn("Test file")
media.filename ="Test file.jpg"
`when`(media?.getCreator()).thenReturn(null)
whenever(media.creator).thenReturn(null)
deleteHelper?.makeDeletion(context, media, "Test reason")?.blockingGet()
deleteHelper.makeDeletion(context, media, "Test reason")?.blockingGet()
}
}

View file

@ -74,11 +74,10 @@ class UploadMediaPresenterTest {
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)
uploadMediaPresenter.receiveImage(uploadableFile, place)
verify(view).showProgress(true)
testScheduler.triggerActions()
verify(view).onImageProcessed(

View file

@ -1,12 +1,12 @@
package fr.free.nrw.commons.wikidata
import com.nhaarman.mockitokotlin2.mock
import fr.free.nrw.commons.wikidata.model.AddEditTagResponse
import fr.free.nrw.commons.wikidata.model.WbCreateClaimResponse
import io.reactivex.Observable
import okhttp3.RequestBody
import org.junit.Before
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.Mock
import org.mockito.Mockito.`when`
@ -37,14 +37,18 @@ class WikidataClientTest {
@Test
fun createClaim() {
`when`(wikidataInterface!!.postCreateClaim(any(RequestBody::class.java),
any(RequestBody::class.java),
any(RequestBody::class.java),
any(RequestBody::class.java),
any(RequestBody::class.java),
any(RequestBody::class.java)))
.thenReturn(Observable.just(mock(WbCreateClaimResponse::class.java)))
wikidataClient!!.createClaim("Q1", "test.jpg")
`when`(
wikidataInterface!!.postCreateClaim(
any(),
any(),
any(),
any(),
any(),
any()
)
)
.thenReturn(Observable.just(mock()))
wikidataClient!!.createImageClaim(mock(), "test.jpg")
}
@Test

View file

@ -1,9 +1,12 @@
package fr.free.nrw.commons.wikidata
import android.content.Context
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verifyZeroInteractions
import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.upload.UploadResult
import fr.free.nrw.commons.upload.WikidataPlace
import fr.free.nrw.commons.wikidata.model.AddEditTagResponse
import io.reactivex.Observable
import org.junit.Before
@ -37,42 +40,27 @@ class WikidataEditServiceTest {
MockitoAnnotations.initMocks(this)
}
@Test
fun noClaimsWhenEntityIdIsNull() {
wikidataEditService.createClaimWithLogging(null, null,"Test.jpg","")
verifyZeroInteractions(wikidataClient)
}
@Test
fun noClaimsWhenFileNameIsNull() {
wikidataEditService.createClaimWithLogging("Q1", "Test", null,"")
verifyZeroInteractions(wikidataClient)
}
@Test
fun noClaimsWhenP18IsNotEmpty() {
wikidataEditService.createClaimWithLogging("Q1", "Test","Test.jpg","Previous.jpg")
verifyZeroInteractions(wikidataClient)
}
@Test
fun noClaimsWhenLocationIsNotCorrect() {
whenever(directKvStore.getBoolean("Picture_Has_Correct_Location", true))
.thenReturn(false)
wikidataEditService.createClaimWithLogging("Q1", "", "Test.jpg", "")
wikidataEditService.createImageClaim(mock(), mock())
verifyZeroInteractions(wikidataClient)
}
@Test
fun createClaimWithLogging() {
fun createImageClaim() {
whenever(directKvStore.getBoolean("Picture_Has_Correct_Location", true))
.thenReturn(true)
whenever(wikidataClient.createClaim(anyString(), anyString()))
whenever(wikidataClient.createImageClaim(any(), any()))
.thenReturn(Observable.just(1L))
whenever(wikidataClient.addEditTag(anyLong(), anyString(), anyString()))
.thenReturn(Observable.just(mock(AddEditTagResponse::class.java)))
whenever(wikibaseClient.getFileEntityId(any())).thenReturn(Observable.just(1L))
wikidataEditService.createClaimWithLogging("Q1", "", "Test.jpg", "")
verify(wikidataClient, times(1)).createClaim(anyString(), anyString())
val wikidataPlace:WikidataPlace = mock()
val uploadResult = mock<UploadResult>()
whenever(uploadResult.filename).thenReturn("file")
wikidataEditService.createImageClaim(wikidataPlace, uploadResult)
verify(wikidataClient, times(1)).createImageClaim(wikidataPlace, """"file"""")
}
}