#3780 Create media using a combination of Entities & MwQueryResult (#3786)

* #3468 Switch from RvRenderer to AdapterDelegates - replace SearchDepictionsRenderer

* #3468 Switch from RvRenderer to AdapterDelegates - replace UploadCategoryDepictionsRenderer

* #3468 Switch from RvRenderer to AdapterDelegates - update BaseAdapter to be easier to use

* #3468 Switch from RvRenderer to AdapterDelegates - replace SearchImagesRenderer

* #3468 Switch from RvRenderer to AdapterDelegates - replace SearchCategoriesRenderer

* #3468 Switch from RvRenderer to AdapterDelegates - replace NotificationRenderer

* #3468 Switch from RvRenderer to AdapterDelegates - replace UploadDepictsRenderer

* #3468 Switch from RvRenderer to AdapterDelegates - replace PlaceRenderer

* #3756 Convert SearchDepictionsFragment to use Pagination - convert SearchDepictionsFragment

* #3756 Convert SearchDepictionsFragment to use Pagination - fix presenter unit tests now that view is not nullable - fix Category prefix imports

* #3756 Convert SearchDepictionsFragment to use Pagination - test DataSource related classes

* #3756 Convert SearchDepictionsFragment to use Pagination - reset rx scheduler - ignore failing test

* #3760 Convert SearchCategoriesFragment to use Pagination - extract functionality of pagination to base classes - add category pagination

* #3772 Convert SearchImagesFragment to use Pagination  - convert SearchImagesFragment - tidy up showing the empty view - make search fragments show snackbar with appropriate text

* #3772 Convert SearchImagesFragment to use Pagination  - allow viewpager to load more data

* #3760 remove test that got re-added by merge

* #3760 remove duplicate dependency

* #3772 fix compilation

* #3780 Create media using a combination of Entities & MwQueryResult - construct media with an entity - move fields from media down to contribution - move dynamic fields outside of media - remove unused constructors - remove all unnecessary fetching of captions/descriptions - bump database version

* #3808 Construct media objects that depict an item id correctly - use generator to construct media for DepictedImages

* #3780 Create media using a combination of Entities & MwQueryResult - update wikicode to align with expected behaviour

* #3780 Create media using a combination of Entities & MwQueryResult - replace old site of thumbnail title with most relevant caption
This commit is contained in:
Seán Mac Gillicuddy 2020-06-25 08:20:01 +01:00 committed by GitHub
parent bf4b7e2efc
commit 4b22583b60
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 803 additions and 1532 deletions

View file

@ -1,6 +1,5 @@
package fr.free.nrw.commons;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
@ -8,44 +7,25 @@ import androidx.annotation.Nullable;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.media.Depictions;
import fr.free.nrw.commons.utils.CommonsDateUtil;
import fr.free.nrw.commons.utils.MediaDataExtractorUtil;
import java.text.ParseException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.wikipedia.dataclient.mwapi.MwQueryPage;
import org.wikipedia.gallery.ExtMetadata;
import org.wikipedia.gallery.ImageInfo;
import org.jetbrains.annotations.NotNull;
import org.wikipedia.page.PageTitle;
@Entity
public class Media implements Parcelable {
public static final Media EMPTY = new Media("");
// Primary metadata fields
@Nullable
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;
private String description; // monolingual description on input...
private String discussion;
private long dataLength;
private Date dateCreated;
private String fallbackDescription; // monolingual description on input...
@Nullable private Date dateUploaded;
private String license;
private String licenseUrl;
@ -57,13 +37,14 @@ public class Media implements Parcelable {
@NonNull
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
*/
private Depictions depictions;
private boolean requestedDeletion;
@Nullable private LatLng coordinates;
@NotNull
private Map<String, String> captions = Collections.emptyMap();
@NotNull
private Map<String, String> descriptions = Collections.emptyMap();
@NotNull
private List<String> depictionIds = Collections.emptyList();
/**
* Provides local constructor
@ -72,6 +53,82 @@ public class Media implements Parcelable {
pageId = UUID.randomUUID().toString();
}
/**
* Constructor with all parameters
*/
public Media(final String thumbUrl,
final String imageUrl,
final String filename,
final String fallbackDescription,
@Nullable final Date dateUploaded,
final String license,
final String licenseUrl,
final String creator,
@NonNull final String pageId,
final List<String> categories,
@Nullable final LatLng coordinates,
@NotNull final Map<String, String> captions,
@NotNull final Map<String, String> descriptions,
@NotNull final List<String> depictionIds) {
this.thumbUrl = thumbUrl;
this.imageUrl = imageUrl;
this.filename = filename;
this.fallbackDescription = fallbackDescription;
this.dateUploaded = dateUploaded;
this.license = license;
this.licenseUrl = licenseUrl;
this.creator = creator;
this.pageId = pageId;
this.categories = categories;
this.coordinates = coordinates;
this.captions = captions;
this.descriptions = descriptions;
this.depictionIds = depictionIds;
}
public Media(Media media) {
this(media.getThumbUrl(), media.getImageUrl(), media.getFilename(),
media.getFallbackDescription(), media.getDateUploaded(), media.getLicense(),
media.getLicenseUrl(), media.getCreator(), media.getPageId(), media.getCategories(),
media.getCoordinates(), media.getCaptions(), media.getDescriptions(),
media.getDepictionIds());
}
public Media(final String filename,
Map<String, String> captions, final String fallbackDescription,
final String creator, final List<String> categories) {
this();
thumbUrl = null;
this.imageUrl = null;
this.filename = filename;
this.fallbackDescription = fallbackDescription;
this.dateUploaded = new Date();
this.creator = creator;
this.categories = categories;
this.captions=captions;
}
protected Media(final Parcel in) {
this(in.readString(), in.readString(), in.readString(),
in.readString(), readDateUploaded(in), in.readString(),
in.readString(), in.readString(), in.readString(), readList(in),
in.readParcelable(LatLng.class.getClassLoader()),
((Map<String, String>) in.readSerializable()),
((Map<String, String>) in.readSerializable()),
readList(in));
}
private static List<String> readList(Parcel in) {
final List<String> list = new ArrayList<>();
in.readStringList(list);
return list;
}
private static Date readDateUploaded(Parcel in) {
final long tmpDateUploaded = in.readLong();
return tmpDateUploaded == -1 ? null : new Date(tmpDateUploaded);
}
public static final Creator<Media> CREATOR = new Creator<Media>() {
@Override
public Media createFromParcel(final Parcel source) {
@ -84,192 +141,6 @@ public class Media implements Parcelable {
}
};
/**
* Provides a minimal constructor
*
* @param filename Media filename
*/
public Media(final String filename) {
this();
this.filename = filename;
}
/**
* Provide Media constructor
* @param localUri Media URI
* @param imageUrl Media image URL
* @param filename Media filename
* @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(final Uri localUri, final String imageUrl, final String filename,
final String description,
final long dataLength, final Date dateCreated, final Date dateUploaded,
final String creator) {
this();
this.localUri = localUri;
thumbUrl = imageUrl;
this.imageUrl = imageUrl;
this.filename = filename;
this.description = description;
this.dataLength = dataLength;
this.dateCreated = dateCreated;
this.dateUploaded = dateUploaded;
this.creator = creator;
}
/**
* Constructor with all parameters
*/
public Media(final String pageId,
final Uri localUri,
final String thumbUrl,
final String imageUrl,
final String filename,
final String description,
final String discussion,
final long dataLength,
final Date dateCreated,
final Date dateUploaded,
final String license,
final String licenseUrl,
final String creator,
final List<String> categories,
final boolean requestedDeletion,
final LatLng coordinates) {
this.pageId = pageId;
this.localUri = localUri;
this.thumbUrl = thumbUrl;
this.imageUrl = imageUrl;
this.filename = filename;
this.description = description;
this.discussion = discussion;
this.dataLength = dataLength;
this.dateCreated = dateCreated;
this.dateUploaded = dateUploaded;
this.license = license;
this.licenseUrl = licenseUrl;
this.creator = creator;
this.categories = categories;
this.requestedDeletion = requestedDeletion;
this.coordinates = coordinates;
}
public Media(final Uri localUri, final String filename,
final String description, final String creator, final List<String> categories) {
this(localUri,null, filename,
description, -1, null, new Date(), creator);
this.categories = categories;
}
public Media(final String title, final Date date, final String user) {
this(null, null, title, "", -1, date, date, user);
}
protected Media(final Parcel in) {
localUri = in.readParcelable(Uri.class.getClassLoader());
thumbUrl = in.readString();
imageUrl = in.readString();
filename = in.readString();
thumbnailTitle = in.readString();
caption = in.readString();
description = in.readString();
discussion = in.readString();
dataLength = in.readLong();
final long tmpDateCreated = in.readLong();
dateCreated = tmpDateCreated == -1 ? null : new Date(tmpDateCreated);
final long tmpDateUploaded = in.readLong();
dateUploaded = tmpDateUploaded == -1 ? null : new Date(tmpDateUploaded);
license = in.readString();
licenseUrl = in.readString();
creator = in.readString();
pageId = in.readString();
final ArrayList<String> list = new ArrayList<>();
in.readStringList(list);
categories = list;
in.readParcelable(Depictions.class.getClassLoader());
requestedDeletion = in.readByte() != 0;
coordinates = in.readParcelable(LatLng.class.getClassLoader());
}
/**
* Creating Media object from MWQueryPage.
* Earlier only basic details were set for the media object but going forward,
* a full media object(with categories, descriptions, coordinates etc) can be constructed using this method
*
* @param page response from the API
* @return Media object
*/
@NonNull
public static Media from(final MwQueryPage page) {
final ImageInfo imageInfo = page.imageInfo();
if (imageInfo == null) {
return new Media(); // null is not allowed
}
final ExtMetadata metadata = imageInfo.getMetadata();
if (metadata == null) {
final Media media = new Media(null, imageInfo.getOriginalUrl(),
page.title(), "", 0, null, null, null);
if (!StringUtils.isBlank(imageInfo.getThumbUrl())) {
media.setThumbUrl(imageInfo.getThumbUrl());
}
return media;
}
final Media media = new Media(null,
imageInfo.getOriginalUrl(),
page.title(),
"",
0,
safeParseDate(metadata.dateTime()),
safeParseDate(metadata.dateTime()),
getArtist(metadata)
);
if (!StringUtils.isBlank(imageInfo.getThumbUrl())) {
media.setThumbUrl(imageInfo.getThumbUrl());
}
media.setPageId(String.valueOf(page.pageId()));
String language = Locale.getDefault().getLanguage();
if (StringUtils.isBlank(language)) {
language = "default";
}
media.setDescription(metadata.imageDescription());
media.setCategories(MediaDataExtractorUtil.extractCategoriesFromList(metadata.getCategories()));
final String latitude = metadata.getGpsLatitude();
final String longitude = metadata.getGpsLongitude();
if (!StringUtils.isBlank(latitude) && !StringUtils.isBlank(longitude)) {
final LatLng latLng = new LatLng(Double.parseDouble(latitude),
Double.parseDouble(longitude), 0);
media.setCoordinates(latLng);
}
media.setLicenseInformation(metadata.licenseShortName(), metadata.licenseUrl());
return media;
}
/**
* This method extracts the Commons Username from the artist HTML information
* @param metadata
* @return
*/
private static String getArtist(final ExtMetadata metadata) {
try {
final String artistHtml = metadata.artist();
return artistHtml.substring(artistHtml.indexOf("title=\""), artistHtml.indexOf("\">"))
.replace("title=\"User:", "");
} catch (final Exception ex) {
return "";
}
}
@Nullable
public String getThumbUrl() {
return thumbUrl;
@ -283,23 +154,6 @@ public class Media implements Parcelable {
return filename != null ? getPageTitle().getDisplayTextWithoutNamespace().replaceFirst("[.][^.]+$", "") : "";
}
@Nullable
private static Date safeParseDate(final String dateStr) {
try {
return CommonsDateUtil.getMediaSimpleDateFormat().parse(dateStr);
} catch (final ParseException e) {
return null;
}
}
/**
* @return title to be shown on image thumbnail
* If caption is available for the image then it returns caption else filename
*/
public String getThumbnailTitle() {
return thumbnailTitle != null? thumbnailTitle : getDisplayTitle();
}
/**
* Gets file page title
* @return New media page title
@ -308,13 +162,6 @@ public class Media implements Parcelable {
return Utils.getPageTitle(getFilename());
}
/**
* Gets local URI
* @return Media local URI
*/
public Uri getLocalUri() {
return localUri;
}
/**
* Gets image URL
@ -348,38 +195,12 @@ public class Media implements Parcelable {
this.pageId = pageId;
}
/**
* Gets the file discussion as a string.
* @return file discussion as a string
*/
public String getDiscussion() {
return discussion;
}
/**
* Gets the file description.
* @return file description as a string
*/
public String getDescription() {
return description;
}
/**
* 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
*
* @return caption
*/
public String getCaption() {
return caption;
}
/**
* @return depictions associated with the current media
*/
public Depictions getDepiction() {
return depictions;
public String getFallbackDescription() {
return fallbackDescription;
}
/**
@ -390,36 +211,12 @@ public class Media implements Parcelable {
this.filename = filename;
}
/**
* Gets the dataLength of the file.
* @return file dataLength as a long
*/
public long getDataLength() {
return dataLength;
}
/**
* Sets the discussion of the file.
* @param discussion
*/
public void setDiscussion(final String discussion) {
this.discussion = discussion;
}
/**
* Gets the creation date of the file.
* @return creation date as a Date
*/
public Date getDateCreated() {
return dateCreated;
}
/**
* Sets the file description.
* @param description the new description of the file
* @param fallbackDescription the new description of the file
*/
public void setDescription(final String description) {
this.description = description;
public void setFallbackDescription(final String fallbackDescription) {
this.fallbackDescription = fallbackDescription;
}
/**
@ -440,14 +237,6 @@ public class Media implements Parcelable {
return creator;
}
/**
* Sets the dataLength of the file.
* @param dataLength as a long
*/
public void setDataLength(final long dataLength) {
this.dataLength = dataLength;
}
/**
* Gets the license name of the file.
* @return license as a String
@ -456,13 +245,6 @@ public class Media implements Parcelable {
return license;
}
/**
* Set Caption(if available) as the thumbnail title of the image
*/
public void setThumbnailTitle(final String title) {
thumbnailTitle = title;
}
public String getLicenseUrl() {
return licenseUrl;
}
@ -492,24 +274,10 @@ public class Media implements Parcelable {
* Gets the categories the file falls under.
* @return file categories as an ArrayList of Strings
*/
@SuppressWarnings("unchecked")
public List<String> getCategories() {
return categories;
}
/**
* Sets the license name of the file.
* @param license license name as a String
*/
public void setLicenseInformation(final String license, String licenseUrl) {
this.license = license;
if (!licenseUrl.startsWith("http://") && !licenseUrl.startsWith("https://")) {
licenseUrl = "https://" + licenseUrl;
}
this.licenseUrl = licenseUrl;
}
/**
* Sets the coordinates of where the file was created.
* @param coordinates file coordinates as a LatLng
@ -523,7 +291,18 @@ public class Media implements Parcelable {
* @return
*/
public String getWikiCode() {
return String.format("[[%s|thumb|%s]]", filename, thumbnailTitle);
return String.format("[[%s|thumb|%s]]", filename, getMostRelevantCaption());
}
public String getMostRelevantCaption() {
final String languageAppropriateCaption = captions.get(Locale.getDefault().getLanguage());
if (languageAppropriateCaption != null) {
return languageAppropriateCaption;
}
for (String firstCaption : captions.values()) {
return firstCaption;
}
return getDisplayTitle();
}
/**
@ -537,21 +316,6 @@ public class Media implements Parcelable {
this.categories = categories;
}
/**
* Get the value of requested deletion
* @return boolean requestedDeletion
*/
public boolean isRequestedDeletion(){
return requestedDeletion;
}
/**
* Set requested deletion to true
* @param requestedDeletion
*/
public void setRequestedDeletion(final boolean requestedDeletion) {
this.requestedDeletion = requestedDeletion;
}
/**
* Sets the license name of the file.
@ -562,22 +326,6 @@ public class Media implements Parcelable {
this.license = license;
}
/**
* 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
*
* This function sets captions
* @param caption
*/
public void setCaption(final String caption) {
this.caption = caption;
}
public void setLocalUri(@Nullable final Uri localUri) {
this.localUri = localUri;
}
public void setImageUrl(final String imageUrl) {
this.imageUrl = imageUrl;
}
@ -590,28 +338,11 @@ public class Media implements Parcelable {
this.licenseUrl = licenseUrl;
}
public Depictions getDepictions() {
return depictions;
}
@Override
public int describeContents() {
return 0;
}
/* Sets depictions for the current media obtained fro Wikibase API*/
public void setDepictions(final Depictions depictions) {
this.depictions = depictions;
}
/**
* Sets the creation date of the file.
* @param date creation date as a Date
*/
public void setDateCreated(final Date date) {
dateCreated = date;
}
/**
* Creates a way to transfer information between two or more
* activities.
@ -620,69 +351,76 @@ public class Media implements Parcelable {
*/
@Override
public void writeToParcel(final Parcel dest, final int flags) {
dest.writeParcelable(localUri, flags);
dest.writeString(thumbUrl);
dest.writeString(imageUrl);
dest.writeString(filename);
dest.writeString(thumbnailTitle);
dest.writeString(caption);
dest.writeString(description);
dest.writeString(discussion);
dest.writeLong(dataLength);
dest.writeLong(dateCreated != null ? dateCreated.getTime() : -1);
dest.writeString(fallbackDescription);
dest.writeLong(dateUploaded != null ? dateUploaded.getTime() : -1);
dest.writeString(license);
dest.writeString(licenseUrl);
dest.writeString(creator);
dest.writeString(pageId);
dest.writeStringList(categories);
dest.writeParcelable(depictions, flags);
dest.writeByte(requestedDeletion ? (byte) 1 : (byte) 0);
dest.writeParcelable(coordinates, flags);
dest.writeSerializable((Serializable) captions);
dest.writeSerializable((Serializable) descriptions);
dest.writeList(depictionIds);
}
public Map<String, String> getCaptions() {
return captions;
}
public void setCaptions(Map<String, String> captions) {
this.captions = captions;
}
public Map<String, String> getDescriptions() {
return descriptions;
}
public void setDescriptions(Map<String, String> descriptions) {
this.descriptions = descriptions;
}
public List<String> getDepictionIds() {
return depictionIds;
}
public void setDepictionIds(final List<String> depictionIds) {
this.depictionIds = depictionIds;
}
/**
* Equals implementation that matches all parameters for equality check
*/
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Media)) {
if (o == null || getClass() != o.getClass()) {
return false;
}
final Media media = (Media) o;
return getDataLength() == media.getDataLength() &&
isRequestedDeletion() == media.isRequestedDeletion() &&
Objects.equals(getLocalUri(), media.getLocalUri()) &&
Objects.equals(getThumbUrl(), media.getThumbUrl()) &&
Objects.equals(getImageUrl(), media.getImageUrl()) &&
Objects.equals(getFilename(), media.getFilename()) &&
Objects.equals(getThumbnailTitle(), media.getThumbnailTitle()) &&
Objects.equals(getCaption(), media.getCaption()) &&
Objects.equals(getDescription(), media.getDescription()) &&
Objects.equals(getDiscussion(), media.getDiscussion()) &&
Objects.equals(getDateCreated(), media.getDateCreated()) &&
Objects.equals(getDateUploaded(), media.getDateUploaded()) &&
Objects.equals(getLicense(), media.getLicense()) &&
Objects.equals(getLicenseUrl(), media.getLicenseUrl()) &&
Objects.equals(getCreator(), media.getCreator()) &&
getPageId().equals(media.getPageId()) &&
Objects.equals(getCategories(), media.getCategories()) &&
Objects.equals(getDepictions(), media.getDepictions()) &&
Objects.equals(getCoordinates(), media.getCoordinates());
return Objects.equals(thumbUrl, media.thumbUrl) &&
Objects.equals(imageUrl, media.imageUrl) &&
Objects.equals(filename, media.filename) &&
Objects.equals(fallbackDescription, media.fallbackDescription) &&
Objects.equals(dateUploaded, media.dateUploaded) &&
Objects.equals(license, media.license) &&
Objects.equals(licenseUrl, media.licenseUrl) &&
Objects.equals(creator, media.creator) &&
pageId.equals(media.pageId) &&
Objects.equals(categories, media.categories) &&
Objects.equals(coordinates, media.coordinates) &&
captions.equals(media.captions) &&
descriptions.equals(media.descriptions) &&
depictionIds.equals(media.depictionIds);
}
/**
* Hashcode implementation that uses all parameters for calculating hash
*/
@Override
public int hashCode() {
return Objects
.hash(getLocalUri(), getThumbUrl(), getImageUrl(), getFilename(), getThumbnailTitle(),
getCaption(), getDescription(), getDiscussion(), getDataLength(), getDateCreated(),
getDateUploaded(), getLicense(), getLicenseUrl(), getCreator(), getPageId(),
getCategories(), getDepictions(), isRequestedDeletion(), getCoordinates());
.hash(thumbUrl, imageUrl, filename, fallbackDescription, dateUploaded, license,
licenseUrl,
creator, pageId, categories, coordinates, captions, descriptions, depictionIds);
}
}