With option for associating image with wikipedia article (#3783)

This commit is contained in:
Vivek Maskara 2020-06-16 06:51:06 -07:00 committed by GitHub
parent a4379fde02
commit e4190f3f7d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 979 additions and 479 deletions

View file

@ -518,6 +518,14 @@ public class Media implements Parcelable {
this.coordinates = coordinates;
}
/**
* Returns wikicode to use the media file on a MediaWiki site
* @return
*/
public String getWikiCode() {
return String.format("[[%s|thumb|%s]]", filename, thumbnailTitle);
}
/**
* Sets the categories the file falls under.
* </p>

View file

@ -39,7 +39,7 @@ public class Contribution extends Media {
* 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<>();
private HashMap<String, String> captions = new HashMap<>();
public Contribution() {
}
@ -51,7 +51,7 @@ public class Contribution extends Media {
UploadMediaDetail.formatList(item.getUploadMediaDetails()),
sessionManager.getAuthorName(),
categories);
captions = UploadMediaDetail.formatCaptions(item.getUploadMediaDetails());
captions = new HashMap<>(UploadMediaDetail.formatCaptions(item.getUploadMediaDetails()));
decimalCoords = item.getGpsCoords().getDecimalCoords();
dateCreatedSource = "";
this.depictedItems = depictedItems;
@ -127,11 +127,11 @@ public class Contribution extends Media {
* <p>
* returns list of captions stored in hashmap
*/
public Map<String, String> getCaptions() {
public HashMap<String, String> getCaptions() {
return captions;
}
public void setCaptions(Map<String, String> captions) {
public void setCaptions(HashMap<String, String> captions) {
this.captions = captions;
}
@ -147,7 +147,7 @@ public class Contribution extends Media {
dest.writeLong(transferred);
dest.writeString(decimalCoords);
dest.writeString(dateCreatedSource);
dest.writeSerializable((HashMap) captions);
dest.writeSerializable(captions);
}
/**

View file

@ -53,6 +53,9 @@ public abstract class ContributionDao {
@Query("SELECT * from contribution WHERE filename=:fileName")
public abstract List<Contribution> getContributionWithTitle(String fileName);
@Query("SELECT * from contribution WHERE pageId=:pageId")
public abstract Contribution getContribution(String pageId);
@Query("UPDATE contribution SET state=:state WHERE state in (:toUpdateStates)")
public abstract Single<Integer> updateStates(int state, int[] toUpdateStates);

View file

@ -5,8 +5,10 @@ import static fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_I
import android.net.Uri;
import android.text.TextUtils;
import android.view.View;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
@ -22,147 +24,203 @@ import fr.free.nrw.commons.media.MediaClient;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import org.wikipedia.dataclient.WikiSite;
import timber.log.Timber;
public class ContributionViewHolder extends RecyclerView.ViewHolder {
private final Callback callback;
@BindView(R.id.contributionImage)
SimpleDraweeView imageView;
@BindView(R.id.contributionTitle) TextView titleView;
@BindView(R.id.contributionState) TextView stateView;
@BindView(R.id.contributionSequenceNumber) TextView seqNumView;
@BindView(R.id.contributionProgress) ProgressBar progressView;
@BindView(R.id.failed_image_options) LinearLayout failedImageOptions;
private final Callback callback;
@BindView(R.id.contributionImage)
SimpleDraweeView imageView;
@BindView(R.id.contributionTitle)
TextView titleView;
@BindView(R.id.contributionState)
TextView stateView;
@BindView(R.id.contributionSequenceNumber)
TextView seqNumView;
@BindView(R.id.contributionProgress)
ProgressBar progressView;
@BindView(R.id.image_options)
RelativeLayout imageOptions;
@BindView(R.id.wikipediaButton)
ImageButton addToWikipediaButton;
@BindView(R.id.retryButton)
ImageButton retryButton;
@BindView(R.id.cancelButton)
ImageButton cancelButton;
private int position;
private Contribution contribution;
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
private final MediaClient mediaClient;
private int position;
private Contribution contribution;
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
private final MediaClient mediaClient;
ContributionViewHolder(final View parent, final Callback callback,
final MediaClient mediaClient) {
super(parent);
this.mediaClient = mediaClient;
ButterKnife.bind(this, parent);
this.callback=callback;
ContributionViewHolder(final View parent, final Callback callback,
final MediaClient mediaClient) {
super(parent);
this.mediaClient = mediaClient;
ButterKnife.bind(this, parent);
this.callback = callback;
}
public void init(final int position, final Contribution contribution) {
this.contribution = contribution;
fetchAndDisplayCaption(contribution);
this.position = position;
final String imageSource = chooseImageSource(contribution.getThumbUrl(),
contribution.getLocalUri());
if (!TextUtils.isEmpty(imageSource)) {
final ImageRequest imageRequest =
ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageSource))
.setProgressiveRenderingEnabled(true)
.build();
imageView.setImageRequest(imageRequest);
}
public void init(final int position, final Contribution contribution) {
this.contribution = contribution;
fetchAndDisplayCaption(contribution);
this.position = position;
final String imageSource = chooseImageSource(contribution.getThumbUrl(), contribution.getLocalUri());
if (!TextUtils.isEmpty(imageSource)) {
final ImageRequest imageRequest =
ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageSource))
.setProgressiveRenderingEnabled(true)
.build();
imageView.setImageRequest(imageRequest);
}
seqNumView.setText(String.valueOf(position + 1));
seqNumView.setVisibility(View.VISIBLE);
seqNumView.setText(String.valueOf(position + 1));
seqNumView.setVisibility(View.VISIBLE);
switch (contribution.getState()) {
case Contribution.STATE_COMPLETED:
stateView.setVisibility(View.GONE);
progressView.setVisibility(View.GONE);
failedImageOptions.setVisibility(View.GONE);
stateView.setText("");
break;
case Contribution.STATE_QUEUED:
stateView.setVisibility(View.VISIBLE);
progressView.setVisibility(View.GONE);
stateView.setText(R.string.contribution_state_queued);
failedImageOptions.setVisibility(View.GONE);
break;
case Contribution.STATE_IN_PROGRESS:
stateView.setVisibility(View.GONE);
progressView.setVisibility(View.VISIBLE);
failedImageOptions.setVisibility(View.GONE);
final long total = contribution.getDataLength();
final long transferred = contribution.getTransferred();
if (transferred == 0 || transferred >= total) {
progressView.setIndeterminate(true);
} else {
progressView.setProgress((int)(((double)transferred / (double)total) * 100));
}
break;
case Contribution.STATE_FAILED:
stateView.setVisibility(View.VISIBLE);
stateView.setText(R.string.contribution_state_failed);
progressView.setVisibility(View.GONE);
failedImageOptions.setVisibility(View.VISIBLE);
break;
}
}
/**
* In contributions first we show the title for the image stored in cache,
* then we fetch captions associated with the image and replace title on the thumbnail with caption
*
* @param contribution
*/
private void fetchAndDisplayCaption(final Contribution contribution) {
if ((contribution.getState() != Contribution.STATE_COMPLETED)) {
titleView.setText(contribution.getDisplayTitle());
addToWikipediaButton.setVisibility(View.GONE);
switch (contribution.getState()) {
case Contribution.STATE_COMPLETED:
stateView.setVisibility(View.GONE);
progressView.setVisibility(View.GONE);
imageOptions.setVisibility(View.GONE);
stateView.setText("");
checkIfMediaExistsOnWikipediaPage(contribution);
break;
case Contribution.STATE_QUEUED:
stateView.setVisibility(View.VISIBLE);
progressView.setVisibility(View.GONE);
stateView.setText(R.string.contribution_state_queued);
imageOptions.setVisibility(View.GONE);
break;
case Contribution.STATE_IN_PROGRESS:
stateView.setVisibility(View.GONE);
progressView.setVisibility(View.VISIBLE);
imageOptions.setVisibility(View.GONE);
final long total = contribution.getDataLength();
final long transferred = contribution.getTransferred();
if (transferred == 0 || transferred >= total) {
progressView.setIndeterminate(true);
} else {
final String pageId = contribution.getPageId();
if (pageId != null) {
Timber.d("Fetching caption for %s", contribution.getFilename());
final String wikibaseMediaId = PAGE_ID_PREFIX
+ pageId; // Create Wikibase media id from the page id. Example media id: M80618155 for https://commons.wikimedia.org/wiki/File:Tantanmen.jpeg with has the pageid 80618155
compositeDisposable.add(mediaClient.getCaptionByWikibaseIdentifier(wikibaseMediaId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber -> {
if (!subscriber.trim().equals(MediaClient.NO_CAPTION)) {
titleView.setText(subscriber);
} else {
titleView.setText(contribution.getDisplayTitle());
}
}));
} else {
titleView.setText(contribution.getDisplayTitle());
}
progressView.setProgress((int) (((double) transferred / (double) total) * 100));
}
break;
case Contribution.STATE_FAILED:
stateView.setVisibility(View.VISIBLE);
stateView.setText(R.string.contribution_state_failed);
progressView.setVisibility(View.GONE);
imageOptions.setVisibility(View.VISIBLE);
break;
}
}
/**
* Returns the image source for the image view, first preference is given to thumbUrl if that is
* null, moves to local uri and if both are null return null
*
* @param thumbUrl
* @param localUri
* @return
*/
@Nullable
private String chooseImageSource(final String thumbUrl, final Uri localUri) {
return !TextUtils.isEmpty(thumbUrl) ? thumbUrl :
localUri != null ? localUri.toString() :
null;
/**
* In contributions first we show the title for the image stored in cache, then we fetch captions
* associated with the image and replace title on the thumbnail with caption
*
* @param contribution
*/
private void fetchAndDisplayCaption(final Contribution contribution) {
if ((contribution.getState() != Contribution.STATE_COMPLETED)) {
titleView.setText(contribution.getDisplayTitle());
} else {
final String pageId = contribution.getPageId();
if (pageId != null) {
Timber.d("Fetching caption for %s", contribution.getFilename());
final String wikibaseMediaId = PAGE_ID_PREFIX
+ pageId; // Create Wikibase media id from the page id. Example media id: M80618155 for https://commons.wikimedia.org/wiki/File:Tantanmen.jpeg with has the pageid 80618155
compositeDisposable.add(mediaClient.getCaptionByWikibaseIdentifier(wikibaseMediaId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber -> {
if (!subscriber.trim().equals(MediaClient.NO_CAPTION)) {
titleView.setText(subscriber);
} else {
titleView.setText(contribution.getDisplayTitle());
}
}));
} else {
titleView.setText(contribution.getDisplayTitle());
}
}
}
/**
* Retry upload when it is failed
*/
@OnClick(R.id.retryButton)
public void retryUpload() {
callback.retryUpload(contribution);
/**
* Checks if a media exists on the corresponding Wikipedia article Currently the check is made for
* the device's current language Wikipedia
*
* @param contribution
*/
private void checkIfMediaExistsOnWikipediaPage(final Contribution contribution) {
if (contribution.getWikidataPlace() == null
|| contribution.getWikidataPlace().getWikipediaArticle() == null) {
return;
}
final String wikipediaArticle = contribution.getWikidataPlace().getWikipediaPageTitle();
compositeDisposable.add(mediaClient.doesPageContainMedia(wikipediaArticle)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(mediaExists -> {
displayWikipediaButton(mediaExists);
}));
}
/**
* Delete a failed upload attempt
*/
@OnClick(R.id.cancelButton)
public void deleteUpload() {
callback.deleteUpload(contribution);
/**
* Handle action buttons visibility if the corresponding wikipedia page doesn't contain any media.
* This method needs to control the state of just the scenario where media does not exists as
* other scenarios are already handled in the init method.
*
* @param mediaExists
*/
private void displayWikipediaButton(Boolean mediaExists) {
if (!mediaExists) {
addToWikipediaButton.setVisibility(View.VISIBLE);
cancelButton.setVisibility(View.GONE);
retryButton.setVisibility(View.GONE);
imageOptions.setVisibility(View.VISIBLE);
}
}
@OnClick(R.id.contributionImage)
public void imageClicked(){
callback.openMediaDetail(position);
}
/**
* Returns the image source for the image view, first preference is given to thumbUrl if that is
* null, moves to local uri and if both are null return null
*
* @param thumbUrl
* @param localUri
* @return
*/
@Nullable
private String chooseImageSource(final String thumbUrl, final Uri localUri) {
return !TextUtils.isEmpty(thumbUrl) ? thumbUrl :
localUri != null ? localUri.toString() :
null;
}
/**
* Retry upload when it is failed
*/
@OnClick(R.id.retryButton)
public void retryUpload() {
callback.retryUpload(contribution);
}
/**
* Delete a failed upload attempt
*/
@OnClick(R.id.cancelButton)
public void deleteUpload() {
callback.deleteUpload(contribution);
}
@OnClick(R.id.contributionImage)
public void imageClicked() {
callback.openMediaDetail(position);
}
@OnClick(R.id.wikipediaButton)
public void wikipediaButtonClicked() {
callback.addImageToWikipedia(contribution);
}
}

View file

@ -7,8 +7,9 @@ import androidx.paging.PagedListAdapter;
import androidx.recyclerview.widget.DiffUtil;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.media.MediaClient;
import org.wikipedia.dataclient.WikiSite;
/**
/**
* Represents The View Adapter for the List of Contributions
*/
public class ContributionsListAdapter extends
@ -64,7 +65,8 @@ public class ContributionsListAdapter extends
final int viewType) {
final ContributionViewHolder viewHolder = new ContributionViewHolder(
LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout_contribution, parent, false), callback, mediaClient);
.inflate(R.layout.layout_contribution, parent, false),
callback, mediaClient);
return viewHolder;
}
@ -75,5 +77,7 @@ public class ContributionsListAdapter extends
void deleteUpload(Contribution contribution);
void openMediaDetail(int contribution);
void addImageToWikipedia(Contribution contribution);
}
}

View file

@ -2,9 +2,11 @@ package fr.free.nrw.commons.contributions;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static fr.free.nrw.commons.di.NetworkingModule.NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE;
import android.content.Context;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.view.LayoutInflater;
@ -17,25 +19,29 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.LayoutManager;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.media.MediaClient;
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
import fr.free.nrw.commons.utils.DialogUtil;
import java.util.Locale;
import javax.inject.Inject;
import javax.inject.Named;
import org.wikipedia.dataclient.WikiSite;
/**
* Created by root on 01.06.2018.
*/
public class ContributionsListFragment extends CommonsDaggerSupportFragment implements
ContributionsListContract.View, ContributionsListAdapter.Callback {
ContributionsListContract.View, ContributionsListAdapter.Callback, WikipediaInstructionsDialogFragment.Callback {
private static final String RV_STATE = "rv_scroll_state";
@ -59,6 +65,10 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
@Inject
MediaClient mediaClient;
@Named(NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE)
@Inject
WikiSite languageWikipediaSite;
@Inject
ContributionsListPresenter contributionsListPresenter;
@ -200,7 +210,8 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
final GridLayoutManager layoutManager = (GridLayoutManager) rvContributionsList.getLayoutManager();
final GridLayoutManager layoutManager = (GridLayoutManager) rvContributionsList
.getLayoutManager();
outState.putParcelable(RV_STATE, layoutManager.onSaveInstanceState());
}
@ -232,6 +243,39 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
}
}
/**
* Handle callback for wikipedia icon clicked
*
* @param contribution
*/
@Override
public void addImageToWikipedia(Contribution contribution) {
DialogUtil.showAlertDialog(getActivity(),
getString(R.string.add_picture_to_wikipedia_article_title),
String.format(getString(R.string.add_picture_to_wikipedia_article_desc),
Locale.getDefault().getDisplayLanguage()),
() -> {
showAddImageToWikipediaInstructions(contribution);
}, () -> {
// do nothing
});
}
/**
* Display confirmation dialog with instructions when the user tries to add image to wikipedia
*
* @param contribution
*/
private void showAddImageToWikipediaInstructions(Contribution contribution) {
FragmentManager fragmentManager = getFragmentManager();
WikipediaInstructionsDialogFragment fragment = WikipediaInstructionsDialogFragment
.newInstance(contribution);
fragment.setCallback(this::onConfirmClicked);
fragment.show(fragmentManager, "WikimediaFragment");
}
public Media getMediaAtPosition(final int i) {
return adapter.getContributionForPosition(i);
}
@ -240,6 +284,23 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
return adapter.getItemCount();
}
/**
* Open the editor for the language Wikipedia
*
* @param contribution
*/
@Override
public void onConfirmClicked(@Nullable Contribution contribution, boolean copyWikicode) {
if(copyWikicode) {
String wikicode = contribution.getWikiCode();
Utils.copy("wikicode", wikicode, getContext());
}
final String url = languageWikipediaSite.mobileUrl() + "/wiki/" + contribution.getWikidataPlace()
.getWikipediaPageTitle();
Utils.handleWebUrl(getContext(), Uri.parse(url));
}
public interface Callback {
void retryUpload(Contribution contribution);

View file

@ -2,6 +2,7 @@ package fr.free.nrw.commons.contributions;
import androidx.paging.DataSource.Factory;
import io.reactivex.Completable;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
@ -67,7 +68,15 @@ class ContributionsLocalDataSource {
}
public Single<List<Long>> saveContributions(List<Contribution> contributions) {
return contributionDao.save(contributions);
List<Contribution> contributionList = new ArrayList<>();
for(Contribution contribution: contributions) {
Contribution oldContribution = contributionDao.getContribution(contribution.getPageId());
if(oldContribution != null) {
contribution.setWikidataPlace(oldContribution.getWikidataPlace());
}
contributionList.add(contribution);
}
return contributionDao.save(contributionList);
}
public void set(String key, long value) {

View file

@ -0,0 +1,67 @@
package fr.free.nrw.commons.contributions
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.fragment.app.DialogFragment
import fr.free.nrw.commons.R
import kotlinx.android.synthetic.main.dialog_add_to_wikipedia_instructions.*
/**
* Dialog fragment for displaying instructions for editing wikipedia
*/
class WikipediaInstructionsDialogFragment : DialogFragment() {
var contribution: Contribution? = null
var callback: Callback? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.dialog_add_to_wikipedia_instructions, container)
}
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
contribution = arguments!!.getParcelable(ARG_CONTRIBUTION)
tv_wikicode.setText(contribution?.wikiCode)
instructions_cancel.setOnClickListener {
dismiss()
}
instructions_confirm.setOnClickListener {
callback?.onConfirmClicked(contribution, checkbox_copy_wikicode.isChecked)
}
dialog!!.window.setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN
)
}
/**
* Callback for handling confirm button clicked
*/
interface Callback {
fun onConfirmClicked(contribution: Contribution?, copyWikicode: Boolean)
}
companion object {
val ARG_CONTRIBUTION = "contribution"
@JvmStatic
fun newInstance(contribution: Contribution): WikipediaInstructionsDialogFragment {
val frag = WikipediaInstructionsDialogFragment()
val args = Bundle()
args.putParcelable(ARG_CONTRIBUTION, contribution)
frag.arguments = args
return frag
}
}
}

View file

@ -11,6 +11,7 @@ import fr.free.nrw.commons.media.Depictions;
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;
@ -54,13 +55,13 @@ public class Converters {
}
@TypeConverter
public static String mapObjectToString(Map<String,String> objectList) {
public static String mapObjectToString(HashMap<String,String> objectList) {
return writeObjectToString(objectList);
}
@TypeConverter
public static Map<String,String> stringToMap(String objectList) {
return readObjectWithTypeToken(objectList, new TypeToken<Map<String,String>>(){});
public static HashMap<String,String> stringToMap(String objectList) {
return readObjectWithTypeToken(objectList, new TypeToken<HashMap<String,String>>(){});
}
@TypeConverter

View file

@ -13,6 +13,7 @@ import fr.free.nrw.commons.explore.depictions.DepictsClient;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.media.MediaDetailInterface;
import fr.free.nrw.commons.media.MediaInterface;
import fr.free.nrw.commons.media.PageMediaInterface;
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
import fr.free.nrw.commons.mwapi.UserInterface;
import fr.free.nrw.commons.review.ReviewInterface;
@ -21,6 +22,7 @@ import fr.free.nrw.commons.upload.WikiBaseInterface;
import fr.free.nrw.commons.upload.depicts.DepictsInterface;
import fr.free.nrw.commons.wikidata.WikidataInterface;
import java.io.File;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import javax.inject.Named;
import javax.inject.Singleton;
@ -49,6 +51,9 @@ public class NetworkingModule {
public static final String NAMED_COMMONS_WIKI_SITE = "commons-wikisite";
private static final String NAMED_WIKI_DATA_WIKI_SITE = "wikidata-wikisite";
private static final String NAMED_WIKI_PEDIA_WIKI_SITE = "wikipedia-wikisite";
public static final String NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE = "language-wikipedia-wikisite";
public static final String NAMED_COMMONS_CSRF = "commons-csrf";
@ -234,4 +239,21 @@ public class NetworkingModule {
public WikidataInterface provideWikidataInterface(@Named(NAMED_WIKI_DATA_WIKI_SITE) WikiSite wikiDataWikiSite) {
return ServiceFactory.get(wikiDataWikiSite, BuildConfig.WIKIDATA_URL, WikidataInterface.class);
}
/**
* Add provider for PageMediaInterface
* It creates a retrofit service for the wiki site using device's current language
*/
@Provides
@Singleton
public PageMediaInterface providePageMediaInterface(@Named(NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE) WikiSite wikiSite) {
return ServiceFactory.get(wikiSite, wikiSite.url(), PageMediaInterface.class);
}
@Provides
@Singleton
@Named(NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE)
public WikiSite provideLanguageWikipediaSite() {
return WikiSite.forLanguageCode(Locale.getDefault().getLanguage());
}
}

View file

@ -28,6 +28,7 @@ import timber.log.Timber;
public class MediaClient {
private final MediaInterface mediaInterface;
private final PageMediaInterface pageMediaInterface;
private final MediaDetailInterface mediaDetailInterface;
//OkHttpJsonApiClient used JsonKvStore for this. I don't know why.
@ -37,8 +38,11 @@ public class MediaClient {
private static final String NO_DEPICTION = "No depiction";
@Inject
public MediaClient(MediaInterface mediaInterface, MediaDetailInterface mediaDetailInterface) {
public MediaClient(MediaInterface mediaInterface,
PageMediaInterface pageMediaInterface,
MediaDetailInterface mediaDetailInterface) {
this.mediaInterface = mediaInterface;
this.pageMediaInterface = pageMediaInterface;
this.mediaDetailInterface = mediaDetailInterface;
this.continuationStore = new HashMap<>();
this.continuationExists = new HashMap<>();
@ -222,6 +226,13 @@ public class MediaClient {
.singleOrError();
}
public Single<Boolean> doesPageContainMedia(String title) {
return pageMediaInterface.getMediaList(title)
.map(pageMediaListResponse -> {
return pageMediaListResponse.getItems().size() > 0;
}).singleOrError();
}
private boolean isSuccess(Entities response) {
return response != null && response.getSuccess() == 1 && response.entities() != null;
}

View file

@ -0,0 +1,19 @@
package fr.free.nrw.commons.media
import fr.free.nrw.commons.media.model.PageMediaListResponse
import io.reactivex.Observable
import retrofit2.http.GET
import retrofit2.http.Path
/**
* Interface for MediaWiki Page REST APIs
*/
interface PageMediaInterface {
/**
* Get a list of media used on a page
*
* @param title the title of the page
*/
@GET("api/rest_v1/page/media-list/{title}")
fun getMediaList(@Path("title") title: String?): Observable<PageMediaListResponse?>?
}

View file

@ -0,0 +1,9 @@
package fr.free.nrw.commons.media.model
data class PageMediaListResponse(
val revision: String,
val tid: String,
val items: List<PageMediaListItem>
)
data class PageMediaListItem(val title: String)

View file

@ -125,7 +125,6 @@ public class Place implements Parcelable {
}
String wikiDataLink = siteLinks.getWikidataLink().toString();
Timber.d("Wikidata entity is %s", wikiDataLink);
return wikiDataLink.replace("http://www.wikidata.org/entity/", "");
}

View file

@ -1419,14 +1419,17 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
if (fabGallery.isShown()) {
Timber.d("Gallery button tapped. Place: %s", selectedPlace.toString());
storeSharedPrefs(selectedPlace);
controller.initiateGalleryPick(getActivity(), false);
}
});
}
private void storeSharedPrefs(final Place selectedPlace) {
Timber.d("Store place object %s", selectedPlace.toString());
applicationKvStore.putJson(PLACE_OBJECT, selectedPlace);
Place place = applicationKvStore.getJson(PLACE_OBJECT, Place.class);
Timber.d("Stored place object %s", place.toString());
}
private void updateBookmarkButtonImage(final Place place) {

View file

@ -47,300 +47,335 @@ import timber.log.Timber;
public class UploadService extends CommonsDaggerService {
private static final String EXTRA_PREFIX = "fr.free.nrw.commons.upload";
private static final String EXTRA_PREFIX = "fr.free.nrw.commons.upload";
public static final String ACTION_START_SERVICE = EXTRA_PREFIX + ".upload";
public static final String EXTRA_FILES = EXTRA_PREFIX + ".files";
@Inject WikidataEditService wikidataEditService;
@Inject SessionManager sessionManager;
@Inject
ContributionDao contributionDao;
@Inject UploadClient uploadClient;
@Inject MediaClient mediaClient;
@Inject
@Named(CommonsApplicationModule.MAIN_THREAD)
Scheduler mainThreadScheduler;
@Inject
@Named(CommonsApplicationModule.IO_THREAD) Scheduler ioThreadScheduler;
public static final String ACTION_START_SERVICE = EXTRA_PREFIX + ".upload";
public static final String EXTRA_FILES = EXTRA_PREFIX + ".files";
@Inject
WikidataEditService wikidataEditService;
@Inject
SessionManager sessionManager;
@Inject
ContributionDao contributionDao;
@Inject
UploadClient uploadClient;
@Inject
MediaClient mediaClient;
@Inject
@Named(CommonsApplicationModule.MAIN_THREAD)
Scheduler mainThreadScheduler;
@Inject
@Named(CommonsApplicationModule.IO_THREAD)
Scheduler ioThreadScheduler;
private NotificationManagerCompat notificationManager;
private NotificationCompat.Builder curNotification;
private int toUpload;
private CompositeDisposable compositeDisposable;
private NotificationManagerCompat notificationManager;
private NotificationCompat.Builder curNotification;
private int toUpload;
private CompositeDisposable compositeDisposable;
/**
* The filePath names of unfinished uploads, used to prevent overwriting
*/
private Set<String> unfinishedUploads = new HashSet<>();
/**
* The filePath names of unfinished uploads, used to prevent overwriting
*/
private Set<String> unfinishedUploads = new HashSet<>();
// DO NOT HAVE NOTIFICATION ID OF 0 FOR ANYTHING
// See http://stackoverflow.com/questions/8725909/startforeground-does-not-show-my-notification
// Seriously, Android?
public static final int NOTIFICATION_UPLOAD_IN_PROGRESS = 1;
public static final int NOTIFICATION_UPLOAD_FAILED = 3;
// DO NOT HAVE NOTIFICATION ID OF 0 FOR ANYTHING
// See http://stackoverflow.com/questions/8725909/startforeground-does-not-show-my-notification
// Seriously, Android?
public static final int NOTIFICATION_UPLOAD_IN_PROGRESS = 1;
public static final int NOTIFICATION_UPLOAD_FAILED = 3;
protected class NotificationUpdateProgressListener{
protected class NotificationUpdateProgressListener {
String notificationTag;
boolean notificationTitleChanged;
Contribution contribution;
String notificationTag;
boolean notificationTitleChanged;
Contribution contribution;
String notificationProgressTitle;
String notificationFinishingTitle;
NotificationUpdateProgressListener(String notificationTag, String notificationProgressTitle, String notificationFinishingTitle, Contribution contribution) {
this.notificationTag = notificationTag;
this.notificationProgressTitle = notificationProgressTitle;
this.notificationFinishingTitle = notificationFinishingTitle;
this.contribution = contribution;
}
public void onProgress(long transferred, long total) {
if (!notificationTitleChanged) {
curNotification.setContentTitle(notificationProgressTitle);
notificationTitleChanged = true;
contribution.setState(Contribution.STATE_IN_PROGRESS);
}
if (transferred == total) {
// Completed!
curNotification.setContentTitle(notificationFinishingTitle)
.setTicker(notificationFinishingTitle)
.setProgress(0, 100, true);
} else {
curNotification.setProgress(100, (int) (((double) transferred / (double) total) * 100), false);
}
notificationManager.notify(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS, curNotification.build());
contribution.setTransferred(transferred);
compositeDisposable.add(contributionDao.update(contribution)
.subscribeOn(ioThreadScheduler)
.subscribe());
}
String notificationProgressTitle;
String notificationFinishingTitle;
NotificationUpdateProgressListener(String notificationTag, String notificationProgressTitle,
String notificationFinishingTitle, Contribution contribution) {
this.notificationTag = notificationTag;
this.notificationProgressTitle = notificationProgressTitle;
this.notificationFinishingTitle = notificationFinishingTitle;
this.contribution = contribution;
}
@Override
public void onDestroy() {
super.onDestroy();
compositeDisposable.dispose();
Timber.d("UploadService.onDestroy; %s are yet to be uploaded", unfinishedUploads);
public void onProgress(long transferred, long total) {
if (!notificationTitleChanged) {
curNotification.setContentTitle(notificationProgressTitle);
notificationTitleChanged = true;
contribution.setState(Contribution.STATE_IN_PROGRESS);
}
if (transferred == total) {
// Completed!
curNotification.setContentTitle(notificationFinishingTitle)
.setTicker(notificationFinishingTitle)
.setProgress(0, 100, true);
} else {
curNotification
.setProgress(100, (int) (((double) transferred / (double) total) * 100), false);
}
notificationManager
.notify(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS, curNotification.build());
contribution.setTransferred(transferred);
compositeDisposable.add(contributionDao.update(contribution)
.subscribeOn(ioThreadScheduler)
.subscribe());
}
public class UploadServiceLocalBinder extends Binder {
public UploadService getService() {
return UploadService.this;
}
}
@Override
public void onDestroy() {
super.onDestroy();
compositeDisposable.dispose();
Timber.d("UploadService.onDestroy; %s are yet to be uploaded", unfinishedUploads);
}
public class UploadServiceLocalBinder extends Binder {
public UploadService getService() {
return UploadService.this;
}
}
private final IBinder localBinder = new UploadServiceLocalBinder();
private PublishProcessor<Contribution> contributionsToUpload;
@Override
public IBinder onBind(Intent intent) {
return localBinder;
}
@Override
public void onCreate() {
super.onCreate();
CommonsApplication.createNotificationChannel(getApplicationContext());
compositeDisposable = new CompositeDisposable();
notificationManager = NotificationManagerCompat.from(this);
curNotification = getNotificationBuilder(CommonsApplication.NOTIFICATION_CHANNEL_ID_ALL);
contributionsToUpload = PublishProcessor.create();
compositeDisposable.add(contributionsToUpload.subscribe(this::handleUpload));
}
public void handleUpload(Contribution contribution) {
contribution.setState(Contribution.STATE_QUEUED);
contribution.setTransferred(0);
toUpload++;
if (curNotification != null && toUpload != 1) {
curNotification.setContentText(getResources()
.getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload));
Timber.d("%d uploads left", toUpload);
notificationManager
.notify(contribution.getLocalUri().toString(), NOTIFICATION_UPLOAD_IN_PROGRESS,
curNotification.build());
}
private final IBinder localBinder = new UploadServiceLocalBinder();
compositeDisposable.add(contributionDao
.save(contribution)
.subscribeOn(ioThreadScheduler)
.subscribe(() -> uploadContribution(contribution)));
}
private PublishProcessor<Contribution> contributionsToUpload;
private boolean freshStart = true;
@Override
public IBinder onBind(Intent intent) {
return localBinder;
public void queue(Contribution contribution) {
contributionsToUpload.offer(contribution);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (ACTION_START_SERVICE.equals(intent.getAction()) && freshStart) {
compositeDisposable.add(contributionDao.updateStates(Contribution.STATE_FAILED,
new int[]{Contribution.STATE_QUEUED, Contribution.STATE_IN_PROGRESS})
.observeOn(mainThreadScheduler)
.subscribeOn(ioThreadScheduler)
.subscribe());
freshStart = false;
}
return START_REDELIVER_INTENT;
}
@Override
public void onCreate() {
super.onCreate();
CommonsApplication.createNotificationChannel(getApplicationContext());
compositeDisposable = new CompositeDisposable();
notificationManager = NotificationManagerCompat.from(this);
curNotification = getNotificationBuilder(CommonsApplication.NOTIFICATION_CHANNEL_ID_ALL);
contributionsToUpload = PublishProcessor.create();
compositeDisposable.add(contributionsToUpload.subscribe(this::handleUpload));
@SuppressLint("StringFormatInvalid")
private NotificationCompat.Builder getNotificationBuilder(String channelId) {
return new NotificationCompat.Builder(this, channelId).setAutoCancel(true)
.setSmallIcon(R.drawable.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher))
.setAutoCancel(true)
.setOnlyAlertOnce(true)
.setProgress(100, 0, true)
.setOngoing(true)
.setContentIntent(
PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0));
}
@SuppressLint("CheckResult")
private void uploadContribution(Contribution contribution) {
Uri localUri = contribution.getLocalUri();
if (localUri == null || localUri.getPath() == null) {
Timber.d("localUri/path is null");
return;
}
String notificationTag = localUri.toString();
File localFile = new File(localUri.getPath());
public void handleUpload(Contribution contribution) {
contribution.setState(Contribution.STATE_QUEUED);
contribution.setTransferred(0);
toUpload++;
if (curNotification != null && toUpload != 1) {
curNotification.setContentText(getResources().getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload));
Timber.d("%d uploads left", toUpload);
notificationManager.notify(contribution.getLocalUri().toString(), NOTIFICATION_UPLOAD_IN_PROGRESS, curNotification.build());
}
Timber.d("Before execution!");
curNotification.setContentTitle(getString(R.string.upload_progress_notification_title_start,
contribution.getDisplayTitle()))
.setContentText(getResources()
.getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload,
toUpload))
.setTicker(getString(R.string.upload_progress_notification_title_in_progress,
contribution.getDisplayTitle()))
.setOngoing(true);
notificationManager
.notify(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS, curNotification.build());
compositeDisposable.add(contributionDao
.save(contribution)
.subscribeOn(ioThreadScheduler)
.subscribe(() -> uploadContribution(contribution)));
}
String filename = contribution.getFilename();
private boolean freshStart = true;
NotificationUpdateProgressListener notificationUpdater = new NotificationUpdateProgressListener(
notificationTag,
getString(R.string.upload_progress_notification_title_in_progress,
contribution.getDisplayTitle()),
getString(R.string.upload_progress_notification_title_finishing,
contribution.getDisplayTitle()),
contribution
);
public void queue(Contribution contribution) {
contributionsToUpload.offer(contribution);
}
Observable.fromCallable(() -> "Temp_" + contribution.hashCode() + filename)
.flatMap(stashFilename -> uploadClient
.uploadFileToStash(getApplicationContext(), stashFilename, localFile,
notificationUpdater))
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.doFinally(() -> {
if (filename != null) {
unfinishedUploads.remove(filename);
}
toUpload--;
if (toUpload == 0) {
// Sync modifications right after all uploads are processed
ContentResolver
.requestSync(sessionManager.getCurrentAccount(), BuildConfig.MODIFICATION_AUTHORITY,
new Bundle());
stopForeground(true);
}
})
.flatMap(uploadStash -> {
notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS);
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (ACTION_START_SERVICE.equals(intent.getAction()) && freshStart) {
compositeDisposable.add(contributionDao.updateStates(Contribution.STATE_FAILED, new int[]{Contribution.STATE_QUEUED, Contribution.STATE_IN_PROGRESS})
.observeOn(mainThreadScheduler)
.subscribeOn(ioThreadScheduler)
.subscribe());
freshStart = false;
}
return START_REDELIVER_INTENT;
}
Timber.d("Stash upload response 1 is %s", uploadStash.toString());
@SuppressLint("StringFormatInvalid")
private NotificationCompat.Builder getNotificationBuilder(String channelId) {
return new NotificationCompat.Builder(this, channelId).setAutoCancel(true)
.setSmallIcon(R.drawable.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher))
.setAutoCancel(true)
.setOnlyAlertOnce(true)
.setProgress(100, 0, true)
.setOngoing(true)
.setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0));
}
@SuppressLint("CheckResult")
private void uploadContribution(Contribution contribution) {
Uri localUri = contribution.getLocalUri();
if (localUri == null || localUri.getPath() == null) {
Timber.d("localUri/path is null");
return;
}
String notificationTag = localUri.toString();
File localFile = new File(localUri.getPath());
Timber.d("Before execution!");
curNotification.setContentTitle(getString(R.string.upload_progress_notification_title_start, contribution.getDisplayTitle()))
.setContentText(getResources().getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload))
.setTicker(getString(R.string.upload_progress_notification_title_in_progress, contribution.getDisplayTitle()))
.setOngoing(true);
notificationManager
.notify(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS, curNotification.build());
String filename = contribution.getFilename();
NotificationUpdateProgressListener notificationUpdater = new NotificationUpdateProgressListener(notificationTag,
getString(R.string.upload_progress_notification_title_in_progress, contribution.getDisplayTitle()),
getString(R.string.upload_progress_notification_title_finishing, contribution.getDisplayTitle()),
contribution
);
Observable.fromCallable(() -> "Temp_" + contribution.hashCode() + filename)
.flatMap(stashFilename -> uploadClient
.uploadFileToStash(getApplicationContext(), stashFilename, localFile,
notificationUpdater))
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.doFinally(() -> {
if (filename != null) {
unfinishedUploads.remove(filename);
}
toUpload--;
if (toUpload == 0) {
// Sync modifications right after all uploads are processed
ContentResolver.requestSync(sessionManager.getCurrentAccount(), BuildConfig.MODIFICATION_AUTHORITY, new Bundle());
stopForeground(true);
}
})
.flatMap(uploadStash -> {
notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS);
Timber.d("Stash upload response 1 is %s", uploadStash.toString());
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());
} else {
Timber.d("Contribution upload failed. Wikidata entity won't be edited");
showFailedNotification(contribution);
return Observable.never();
}
})
.subscribe(
uploadResult -> onUpload(contribution, notificationTag, uploadResult),
throwable -> {
Timber.w(throwable, "Exception during upload");
notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS);
showFailedNotification(contribution);
});
}
private void onUpload(Contribution contribution, String notificationTag,
UploadResult uploadResult) {
Timber.d("Stash upload response 2 is %s", uploadResult.toString());
notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS);
if (uploadResult.isSuccessful()) {
onSuccessfulUpload(contribution, uploadResult);
} 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());
} else {
Timber.d("Contribution upload failed. Wikidata entity won't be edited");
showFailedNotification(contribution);
}
}
return Observable.never();
}
})
.subscribe(
uploadResult -> onUpload(contribution, notificationTag, uploadResult),
throwable -> {
Timber.w(throwable, "Exception during upload");
notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS);
showFailedNotification(contribution);
});
}
private void onSuccessfulUpload(Contribution contribution, UploadResult uploadResult) {
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 onUpload(Contribution contribution, String notificationTag,
UploadResult uploadResult) {
Timber.d("Stash upload response 2 is %s", uploadResult.toString());
private void saveCompletedContribution(Contribution contribution, UploadResult uploadResult) {
compositeDisposable.add(mediaClient.getMedia("File:" + uploadResult.getFilename())
.map(media -> new Contribution(media, Contribution.STATE_COMPLETED))
.flatMapCompletable(newContribution -> contributionDao.saveAndDelete(contribution, newContribution))
notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS);
if (uploadResult.isSuccessful()) {
onSuccessfulUpload(contribution, uploadResult);
} else {
Timber.d("Contribution upload failed. Wikidata entity won't be edited");
showFailedNotification(contribution);
}
}
private void onSuccessfulUpload(Contribution contribution, UploadResult uploadResult) {
compositeDisposable
.add(wikidataEditService.addDepictionsAndCaptions(uploadResult, contribution));
WikidataPlace wikidataPlace = contribution.getWikidataPlace();
Timber.d("Wikidata place %s", wikidataPlace.toString());
if (wikidataPlace != null && wikidataPlace.getImageValue() == null) {
wikidataEditService.createImageClaim(wikidataPlace, uploadResult);
}
saveCompletedContribution(contribution, uploadResult);
}
private void saveCompletedContribution(Contribution contribution, UploadResult uploadResult) {
compositeDisposable.add(mediaClient.getMedia("File:" + uploadResult.getFilename())
.map(media -> {
Contribution newContribution = new Contribution(media, Contribution.STATE_COMPLETED);
if (contribution.getWikidataPlace() != null) {
newContribution.setWikidataPlace(contribution.getWikidataPlace());
}
return newContribution;
})
.flatMapCompletable(
newContribution -> contributionDao.saveAndDelete(contribution, newContribution))
.subscribe());
}
}
@SuppressLint("StringFormatInvalid")
@SuppressWarnings("deprecation")
private void showFailedNotification(Contribution contribution) {
curNotification.setTicker(getString(R.string.upload_failed_notification_title, contribution.getDisplayTitle()))
.setContentTitle(getString(R.string.upload_failed_notification_title, contribution.getDisplayTitle()))
.setContentText(getString(R.string.upload_failed_notification_subtitle))
.setProgress(0, 0, false)
.setOngoing(false);
notificationManager.notify(contribution.getLocalUri().toString(), NOTIFICATION_UPLOAD_FAILED, curNotification.build());
@SuppressLint("StringFormatInvalid")
@SuppressWarnings("deprecation")
private void showFailedNotification(Contribution contribution) {
curNotification.setTicker(
getString(R.string.upload_failed_notification_title, contribution.getDisplayTitle()))
.setContentTitle(
getString(R.string.upload_failed_notification_title, contribution.getDisplayTitle()))
.setContentText(getString(R.string.upload_failed_notification_subtitle))
.setProgress(0, 0, false)
.setOngoing(false);
notificationManager.notify(contribution.getLocalUri().toString(), NOTIFICATION_UPLOAD_FAILED,
curNotification.build());
contribution.setState(Contribution.STATE_FAILED);
contribution.setState(Contribution.STATE_FAILED);
compositeDisposable.add(contributionDao
.update(contribution)
.subscribeOn(ioThreadScheduler)
.subscribe());
}
compositeDisposable.add(contributionDao
.update(contribution)
.subscribeOn(ioThreadScheduler)
.subscribe());
}
private String findUniqueFilename(String fileName) throws IOException {
String sequenceFileName;
for (int sequenceNumber = 1; true; sequenceNumber++) {
if (sequenceNumber == 1) {
sequenceFileName = fileName;
} else {
if (fileName.indexOf('.') == -1) {
// We really should have appended a filePath type suffix already.
// But... we might not.
sequenceFileName = fileName + " " + sequenceNumber;
} else {
Pattern regex = Pattern.compile("^(.*)(\\..+?)$");
Matcher regexMatcher = regex.matcher(fileName);
sequenceFileName = regexMatcher.replaceAll("$1 " + sequenceNumber + "$2");
}
}
if (!mediaClient.checkPageExistsUsingTitle(String.format("File:%s",sequenceFileName)).blockingGet()
&& !unfinishedUploads.contains(sequenceFileName)) {
break;
}
private String findUniqueFilename(String fileName) throws IOException {
String sequenceFileName;
for (int sequenceNumber = 1; true; sequenceNumber++) {
if (sequenceNumber == 1) {
sequenceFileName = fileName;
} else {
if (fileName.indexOf('.') == -1) {
// We really should have appended a filePath type suffix already.
// But... we might not.
sequenceFileName = fileName + " " + sequenceNumber;
} else {
Pattern regex = Pattern.compile("^(.*)(\\..+?)$");
Matcher regexMatcher = regex.matcher(fileName);
sequenceFileName = regexMatcher.replaceAll("$1 " + sequenceNumber + "$2");
}
return sequenceFileName;
}
if (!mediaClient.checkPageExistsUsingTitle(String.format("File:%s", sequenceFileName))
.blockingGet()
&& !unfinishedUploads.contains(sequenceFileName)) {
break;
}
}
return sequenceFileName;
}
}

View file

@ -5,12 +5,19 @@ 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 {
internal data class WikidataPlace(
override val id: String,
override val name: String,
val imageValue: String?,
val wikipediaArticle: String?
) :
WikidataItem, Parcelable {
constructor(place: Place) : this(
place.wikiDataEntityId!!,
place.name,
place.pic.takeIf { it.isNotBlank() })
place.pic.takeIf { it.isNotBlank() },
if (place.siteLinks.wikipediaLink == null) "" else place.siteLinks.wikipediaLink.toString()
)
companion object {
@JvmStatic
@ -18,4 +25,12 @@ internal data class WikidataPlace(override val id: String, override val name: St
return place?.let { WikidataPlace(it) }
}
}
fun getWikipediaPageTitle(): String? {
if (wikipediaArticle == null) {
return null
}
val split: Array<String> = wikipediaArticle.split("/".toRegex()).toTypedArray()
return split[split.size - 1]
}
}