#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,
long dataLength, Date dateCreated, Date dateUploaded, String creator) {
this();
public Media(Uri localUri, String imageUrl, String filename,
String description,
long dataLength, Date dateCreated, Date dateUploaded, String creator) {
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,8 +138,7 @@ public class Media implements Parcelable {
Media media = new Media(null,
imageInfo.getOriginalUrl(),
page.title(),
new HashMap<>(),
"",
"",
0,
safeParseDate(metadata.dateTime()),
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,34 +1,22 @@
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";
public class Contribution extends Media {
// No need to be bitwise - they're mutually exclusive
public static final int STATE_COMPLETED = -1;
@ -36,256 +24,133 @@ public class Contribution extends Media {
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,8 +112,7 @@ public class DepictsClient {
Media media = new Media(null,
getUrl(s.getTitle()),
s.getTitle(),
new HashMap<>(),
"",
"",
0,
safeParseDate(s.getTimestamp()),
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,28 +69,23 @@ 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;
}
/**
* pre process a one item at a time
*/
public Observable<UploadItem> preProcessImage(final UploadableFile uploadableFile,
final Place place,
final String source,
final SimilarImageInterface similarImageInterface) {
final Place place,
final SimilarImageInterface similarImageInterface) {
return Observable.just(
createAndAddUploadItem(uploadableFile, place, source, similarImageInterface));
createAndAddUploadItem(uploadableFile, place, similarImageInterface));
}
public Single<Integer> getImageQuality(final UploadItem uploadItem) {
@ -100,9 +93,8 @@ public class UploadModel {
}
private UploadItem createAndAddUploadItem(final UploadableFile uploadableFile,
final Place place,
final String source,
final SimilarImageInterface similarImageInterface) {
final Place place,
final SimilarImageInterface similarImageInterface) {
final UploadableFile.DateTimeWithSource dateTimeWithSource = uploadableFile
.getFileCreatedDate(context);
long fileCreatedDate = -1;
@ -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,17 +215,17 @@ 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 Place place,
final long createdTimestamp,
final String createdTimestampSource) {
final Uri mediaUri, final String mimeType,
final ImageCoordinates gpsCoords,
final Place place,
final long createdTimestamp,
final String createdTimestampSource) {
this.originalContentUri = originalContentUri;
this.createdTimestampSource = createdTimestampSource;
uploadMediaDetails = new ArrayList<>(Arrays.asList(new UploadMediaDetail()));
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,20 +245,19 @@ 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);
return uploadClient.uploadFileFromStash(
getApplicationContext(),
contribution,
uniqueFilename,
uploadStash.getFilekey());
getApplicationContext(),
contribution,
uniqueFilename,
uploadStash.getFilekey());
} else {
Timber.d("Contribution upload failed. Wikidata entity won't be edited");
showFailedNotification(contribution);
return Observable.never();
}
})
.subscribe(
@ -282,44 +275,38 @@ 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);
contribution.setImageUrl(uploadResult.getImageinfo().getOriginalUrl());
contribution.setState(Contribution.STATE_COMPLETED);
contribution.setDateUploaded(CommonsDateUtil.getIso8601DateFormatTimestamp()
.parse(uploadResult.getImageinfo().getTimestamp()));
compositeDisposable.add(contributionDao
.save(contribution)
.subscribeOn(ioThreadScheduler)
.observeOn(mainThreadScheduler)
.subscribe());
}
}
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()
.parse(uploadResult.getImageinfo().getTimestamp()));
compositeDisposable.add(contributionDao
.save(contribution)
.subscribeOn(ioThreadScheduler)
.observeOn(mainThreadScheduler)
.subscribe());
}
@SuppressLint("StringFormatInvalid")
@SuppressWarnings("deprecation")
private void showFailedNotification(Contribution contribution) {

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 uploadableFile
* @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
@ -18,27 +19,43 @@ import static fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF;
@Singleton
public class WikiBaseClient {
private final WikiBaseInterface wikiBaseInterface;
private final CsrfTokenClient csrfTokenClient;
private final WikiBaseInterface wikiBaseInterface;
private final CsrfTokenClient csrfTokenClient;
@Inject
public WikiBaseClient(WikiBaseInterface wikiBaseInterface,
@Named(NAMED_COMMONS_CSRF) CsrfTokenClient csrfTokenClient) {
this.wikiBaseInterface = wikiBaseInterface;
this.csrfTokenClient = csrfTokenClient;
}
@Inject
public WikiBaseClient(WikiBaseInterface wikiBaseInterface,
@Named(NAMED_COMMONS_CSRF) CsrfTokenClient csrfTokenClient) {
this.wikiBaseInterface = wikiBaseInterface;
this.csrfTokenClient = csrfTokenClient;
}
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);
}
}
public Observable<Boolean> postEditEntity(String fileEntityId, String data) {
return csrfToken()
.switchMap(editToken -> wikiBaseInterface.postEditEntity(fileEntityId, editToken, data)
.map(response -> (response.getSuccessVal() == 1)));
}
public Observable<Long> getFileEntityId(String fileName) {
return wikiBaseInterface.getFileEntityId(fileName)
.map(response -> (long) (response.query().pages().get(0).pageId()));
}
}
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,279 +36,193 @@ 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 WikiBaseClient wikiBaseClient;
private final WikidataClient wikidataClient;
private final CsrfTokenClient csrfTokenClient;
@Inject
@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;
}
/**
* Edits the wikibase entity by adding DEPICTS property.
* Adding DEPICTS property requires call to the wikibase API to set tag against the entity.
*/
@SuppressLint("CheckResult")
private Observable<Boolean> addDepictsProperty(final String fileEntityId,
final WikidataItem depictedItem) {
// Wikipedia:Sandbox (Q10)
final String data = depictionJson(ConfigUtils.isBetaFlavour() ? "Q10" : depictedItem.getId());
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);
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("Unable to set DEPICTS property for %s", fileEntityId);
}
})
.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", ""));
value.addProperty("id", entityId);
final JsonObject dataValue = new JsonObject();
dataValue.add("value", value);
dataValue.addProperty("type", "wikibase-entityid");
/**
* 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 JsonObject mainSnak = new JsonObject();
mainSnak.addProperty("snaktype", "value");
mainSnak.addProperty("property", WikidataProperties.DEPICTS.getPropertyName());
mainSnak.add("datavalue", dataValue);
final String propertyValue = getFileName(fileName);
final JsonObject claim = new JsonObject();
claim.add("mainsnak", mainSnak);
claim.addProperty("type", "statement");
claim.addProperty("rank", "preferred");
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));
});
}
final JsonArray claims = new JsonArray();
claims.add(claim);
/**
* 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());
} else {
Timber.d("Error acquiring EntityId for image: %s", fileName);
}
}, throwable -> {
Timber.e(throwable, "Error occurred while getting EntityID to set DEPICTS property");
ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure));
});
}
final JsonObject jsonData = new JsonObject();
jsonData.add("claims", claims);
@SuppressLint("CheckResult")
private void addDepictsProperty(String entityId, final String fileEntityId) {
if (ConfigUtils.isBetaFlavour()) {
entityId = "Q10"; // Wikipedia:Sandbox (Q10)
}
return jsonData.toString();
}
final JsonObject value = new JsonObject();
value.addProperty("entity-type", "item");
value.addProperty("numeric-id", entityId.replace("Q", ""));
value.addProperty("id", entityId);
final JsonObject dataValue = new JsonObject();
dataValue.add("value", value);
dataValue.addProperty("type", "wikibase-entityid");
final JsonObject mainSnak = new JsonObject();
mainSnak.addProperty("snaktype", "value");
mainSnak.addProperty("property", BuildConfig.DEPICTS_PROPERTY);
mainSnak.add("datavalue", dataValue);
final JsonObject claim = new JsonObject();
claim.add("mainsnak", mainSnak);
claim.addProperty("type", "statement");
claim.addProperty("rank", "preferred");
final JsonArray claims = new JsonArray();
claims.add(claim);
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());
});
}
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;
}
})
.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);
} 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");
ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure));
});
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));
})
.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);
}
}
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`
@ -32,25 +32,29 @@ class WikidataClientTest {
`when`(mwQueryResult!!.csrfToken()).thenReturn("test_token")
`when`(mwQueryResponse.query()).thenReturn(mwQueryResult)
`when`(wikidataInterface!!.getCsrfToken())
.thenReturn(Observable.just(mwQueryResponse))
.thenReturn(Observable.just(mwQueryResponse))
}
@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
fun addEditTag() {
`when`(wikidataInterface!!.addEditTag(anyString(), anyString(), anyString(), anyString()))
.thenReturn(Observable.just(mock(AddEditTagResponse::class.java)))
.thenReturn(Observable.just(mock(AddEditTagResponse::class.java)))
wikidataClient!!.addEditTag(1L, "test", "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"""")
}
}