consumer, ProducerContext context) {
+ return new OkHttpNetworkFetchState(consumer, context);
+ }
- fetchState
- .getContext()
- .addCallbacks(
- new BaseProducerContextCallbacks() {
- @Override
- public void onCancellationRequested() {
- if (Looper.myLooper() != Looper.getMainLooper()) {
- call.cancel();
- } else {
- mCancellationExecutor.execute(
- new Runnable() {
- @Override
- public void run() {
- call.cancel();
- }
- });
- }
- }
- });
+ @Override
+ public void fetch(
+ final OkHttpNetworkFetchState fetchState, final NetworkFetcher.Callback callback) {
+ fetchState.submitTime = SystemClock.elapsedRealtime();
+ final Uri uri = fetchState.getUri();
- call.enqueue(
- new okhttp3.Callback() {
- @Override
- public void onResponse(Call call, Response response) throws IOException {
- fetchState.responseTime = SystemClock.elapsedRealtime();
- final ResponseBody body = response.body();
- try {
- if (!response.isSuccessful()) {
- handleException(
- call, new IOException("Unexpected HTTP code " + response), callback);
+ try {
+ if (defaultKvStore
+ .getBoolean(CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED, false)) {
+ Timber.d("Skipping loading of image as limited connection mode is enabled");
+ callback.onFailure(
+ new Exception("Failing image request as limited connection mode is enabled"));
return;
- }
-
- BytesRange responseRange =
- BytesRange.fromContentRangeHeader(response.header("Content-Range"));
- if (responseRange != null
- && !(responseRange.from == 0
- && responseRange.to == BytesRange.TO_END_OF_CONTENT)) {
- // Only treat as a partial image if the range is not all of the content
- fetchState.setResponseBytesRange(responseRange);
- fetchState.setOnNewResultStatusFlags(Consumer.IS_PARTIAL_RESULT);
- }
-
- long contentLength = body.contentLength();
- if (contentLength < 0) {
- contentLength = 0;
- }
- callback.onResponse(body.byteStream(), (int) contentLength);
- } catch (Exception e) {
- handleException(call, e, callback);
- } finally {
- body.close();
}
- }
+ final Request.Builder requestBuilder = new Request.Builder().url(uri.toString()).get();
- @Override
- public void onFailure(Call call, IOException e) {
- handleException(call, e, callback);
- }
- });
- }
+ if (mCacheControl != null) {
+ requestBuilder.cacheControl(mCacheControl);
+ }
- /**
- * Handles exceptions.
- *
- * OkHttp notifies callers of cancellations via an IOException. If IOException is caught after
- * request cancellation, then the exception is interpreted as successful cancellation and
- * onCancellation is called. Otherwise onFailure is called.
- */
- private void handleException(final Call call, final Exception e, final Callback callback) {
- if (call.isCanceled()) {
- callback.onCancellation();
- } else {
- callback.onFailure(e);
+ final BytesRange bytesRange = fetchState.getContext().getImageRequest().getBytesRange();
+ if (bytesRange != null) {
+ requestBuilder.addHeader("Range", bytesRange.toHttpRangeHeaderValue());
+ }
+
+ fetchWithRequest(fetchState, callback, requestBuilder.build());
+ } catch (Exception e) {
+ // handle error while creating the request
+ callback.onFailure(e);
+ }
}
- }
- public static class OkHttpNetworkFetchState extends FetchState {
-
- public long submitTime;
- public long responseTime;
- public long fetchCompleteTime;
-
- public OkHttpNetworkFetchState(
- Consumer consumer, ProducerContext producerContext) {
- super(consumer, producerContext);
+ @Override
+ public void onFetchCompletion(OkHttpNetworkFetchState fetchState, int byteSize) {
+ fetchState.fetchCompleteTime = SystemClock.elapsedRealtime();
+ }
+
+ @Override
+ public Map getExtraMap(OkHttpNetworkFetchState fetchState, int byteSize) {
+ Map extraMap = new HashMap<>(4);
+ extraMap.put(QUEUE_TIME, Long.toString(fetchState.responseTime - fetchState.submitTime));
+ extraMap
+ .put(FETCH_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.responseTime));
+ extraMap
+ .put(TOTAL_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.submitTime));
+ extraMap.put(IMAGE_SIZE, Integer.toString(byteSize));
+ return extraMap;
+ }
+
+ protected void fetchWithRequest(
+ final OkHttpNetworkFetchState fetchState,
+ final NetworkFetcher.Callback callback,
+ final Request request) {
+ final Call call = mCallFactory.newCall(request);
+
+ fetchState
+ .getContext()
+ .addCallbacks(
+ new BaseProducerContextCallbacks() {
+ @Override
+ public void onCancellationRequested() {
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ call.cancel();
+ } else {
+ mCancellationExecutor.execute(
+ new Runnable() {
+ @Override
+ public void run() {
+ call.cancel();
+ }
+ });
+ }
+ }
+ });
+
+ call.enqueue(
+ new okhttp3.Callback() {
+ @Override
+ public void onResponse(Call call, Response response) throws IOException {
+ fetchState.responseTime = SystemClock.elapsedRealtime();
+ final ResponseBody body = response.body();
+ try {
+ if (!response.isSuccessful()) {
+ handleException(
+ call, new IOException("Unexpected HTTP code " + response),
+ callback);
+ return;
+ }
+
+ BytesRange responseRange =
+ BytesRange.fromContentRangeHeader(response.header("Content-Range"));
+ if (responseRange != null
+ && !(responseRange.from == 0
+ && responseRange.to == BytesRange.TO_END_OF_CONTENT)) {
+ // Only treat as a partial image if the range is not all of the content
+ fetchState.setResponseBytesRange(responseRange);
+ fetchState.setOnNewResultStatusFlags(Consumer.IS_PARTIAL_RESULT);
+ }
+
+ long contentLength = body.contentLength();
+ if (contentLength < 0) {
+ contentLength = 0;
+ }
+ callback.onResponse(body.byteStream(), (int) contentLength);
+ } catch (Exception e) {
+ handleException(call, e, callback);
+ } finally {
+ body.close();
+ }
+ }
+
+ @Override
+ public void onFailure(Call call, IOException e) {
+ handleException(call, e, callback);
+ }
+ });
+ }
+
+ /**
+ * Handles exceptions.
+ *
+ * OkHttp notifies callers of cancellations via an IOException. If IOException is caught
+ * after
+ * request cancellation, then the exception is interpreted as successful cancellation and
+ * onCancellation is called. Otherwise onFailure is called.
+ */
+ private void handleException(final Call call, final Exception e, final Callback callback) {
+ if (call.isCanceled()) {
+ callback.onCancellation();
+ } else {
+ callback.onFailure(e);
+ }
+ }
+
+ public static class OkHttpNetworkFetchState extends FetchState {
+
+ public long submitTime;
+ public long responseTime;
+ public long fetchCompleteTime;
+
+ public OkHttpNetworkFetchState(
+ Consumer consumer, ProducerContext producerContext) {
+ super(consumer, producerContext);
+ }
}
- }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java b/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java
index 6b306690b..4b7146487 100644
--- a/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java
+++ b/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java
@@ -42,319 +42,325 @@ import timber.log.Timber;
@Singleton
public class OkHttpJsonApiClient {
- private final OkHttpClient okHttpClient;
- private final DepictsClient depictsClient;
- private final HttpUrl wikiMediaToolforgeUrl;
- private final HttpUrl wikiMediaTestToolforgeUrl;
- private final String sparqlQueryUrl;
- private final String campaignsUrl;
- private final Gson gson;
+ private final OkHttpClient okHttpClient;
+ private final DepictsClient depictsClient;
+ private final HttpUrl wikiMediaToolforgeUrl;
+ private final HttpUrl wikiMediaTestToolforgeUrl;
+ private final String sparqlQueryUrl;
+ private final String campaignsUrl;
+ private final Gson gson;
- @Inject
- public OkHttpJsonApiClient(OkHttpClient okHttpClient,
- DepictsClient depictsClient,
- HttpUrl wikiMediaToolforgeUrl,
- HttpUrl wikiMediaTestToolforgeUrl,
- String sparqlQueryUrl,
- String campaignsUrl,
- Gson gson) {
- this.okHttpClient = okHttpClient;
- this.depictsClient = depictsClient;
- this.wikiMediaToolforgeUrl = wikiMediaToolforgeUrl;
- this.wikiMediaTestToolforgeUrl = wikiMediaTestToolforgeUrl;
- this.sparqlQueryUrl = sparqlQueryUrl;
- this.campaignsUrl = campaignsUrl;
- this.gson = gson;
- }
-
- /**
- * The method will gradually calls the leaderboard API and fetches the leaderboard
- * @param userName username of leaderboard user
- * @param duration duration for leaderboard
- * @param category category for leaderboard
- * @param limit page size limit for list
- * @param offset offset for the list
- * @return LeaderboardResponse object
- */
- @NonNull
- public Observable getLeaderboard(String userName, String duration, String category, String limit, String offset) {
- final String fetchLeaderboardUrlTemplate = wikiMediaTestToolforgeUrl
- + LEADERBOARD_END_POINT;
- String url = String.format(Locale.ENGLISH,
- fetchLeaderboardUrlTemplate,
- userName,
- duration,
- category,
- limit,
- offset);
- HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
- urlBuilder.addQueryParameter("user", userName);
- urlBuilder.addQueryParameter("duration", duration);
- urlBuilder.addQueryParameter("category", category);
- urlBuilder.addQueryParameter("limit", limit);
- urlBuilder.addQueryParameter("offset", offset);
- Timber.i("Url %s", urlBuilder.toString());
- Request request = new Request.Builder()
- .url(urlBuilder.toString())
- .build();
- return Observable.fromCallable(() -> {
- Response response = okHttpClient.newCall(request).execute();
- if (response != null && response.body() != null && response.isSuccessful()) {
- String json = response.body().string();
- if (json == null) {
- return new LeaderboardResponse();
- }
- Timber.d("Response for leaderboard is %s", json);
- try {
- return gson.fromJson(json, LeaderboardResponse.class);
- } catch (Exception e) {
- return new LeaderboardResponse();
- }
- }
- return new LeaderboardResponse();
- });
- }
-
- /**
- * This method will update the leaderboard user avatar
- * @param username username to update
- * @param avatar url of the new avatar
- * @return UpdateAvatarResponse object
- */
- @NonNull
- public Single setAvatar(String username, String avatar) {
- final String urlTemplate = wikiMediaTestToolforgeUrl
- + UPDATE_AVATAR_END_POINT;
- return Single.fromCallable(() -> {
- String url = String.format(Locale.ENGLISH,
- urlTemplate,
- username,
- avatar);
- HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
- urlBuilder.addQueryParameter("user", username);
- urlBuilder.addQueryParameter("avatar", avatar);
- Timber.i("Url %s", urlBuilder.toString());
- Request request = new Request.Builder()
- .url(urlBuilder.toString())
- .build();
- Response response = okHttpClient.newCall(request).execute();
- if (response != null && response.body() != null && response.isSuccessful()) {
- String json = response.body().string();
- if (json == null) {
- return null;
- }
- try {
- return gson.fromJson(json, UpdateAvatarResponse.class);
- } catch (Exception e) {
- return new UpdateAvatarResponse();
- }
- }
- return null;
- });
- }
-
- @NonNull
- public Single getUploadCount(String userName) {
- HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder();
- urlBuilder
- .addPathSegments("uploadsbyuser.py")
- .addQueryParameter("user", userName);
-
- if (ConfigUtils.isBetaFlavour()) {
- urlBuilder.addQueryParameter("labs", "commonswiki");
+ @Inject
+ public OkHttpJsonApiClient(OkHttpClient okHttpClient,
+ DepictsClient depictsClient,
+ HttpUrl wikiMediaToolforgeUrl,
+ HttpUrl wikiMediaTestToolforgeUrl,
+ String sparqlQueryUrl,
+ String campaignsUrl,
+ Gson gson) {
+ this.okHttpClient = okHttpClient;
+ this.depictsClient = depictsClient;
+ this.wikiMediaToolforgeUrl = wikiMediaToolforgeUrl;
+ this.wikiMediaTestToolforgeUrl = wikiMediaTestToolforgeUrl;
+ this.sparqlQueryUrl = sparqlQueryUrl;
+ this.campaignsUrl = campaignsUrl;
+ this.gson = gson;
}
- Request request = new Request.Builder()
- .url(urlBuilder.build())
- .build();
-
- return Single.fromCallable(() -> {
- Response response = okHttpClient.newCall(request).execute();
- if (response != null && response.isSuccessful()) {
- ResponseBody responseBody = response.body();
- if (null != responseBody) {
- String responseBodyString = responseBody.string().trim();
- if (!TextUtils.isEmpty(responseBodyString)) {
- try {
- return Integer.parseInt(responseBodyString);
- } catch (NumberFormatException e) {
- Timber.e(e);
+ /**
+ * The method will gradually calls the leaderboard API and fetches the leaderboard
+ *
+ * @param userName username of leaderboard user
+ * @param duration duration for leaderboard
+ * @param category category for leaderboard
+ * @param limit page size limit for list
+ * @param offset offset for the list
+ * @return LeaderboardResponse object
+ */
+ @NonNull
+ public Observable getLeaderboard(String userName, String duration,
+ String category, String limit, String offset) {
+ final String fetchLeaderboardUrlTemplate = wikiMediaTestToolforgeUrl
+ + LEADERBOARD_END_POINT;
+ String url = String.format(Locale.ENGLISH,
+ fetchLeaderboardUrlTemplate,
+ userName,
+ duration,
+ category,
+ limit,
+ offset);
+ HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
+ urlBuilder.addQueryParameter("user", userName);
+ urlBuilder.addQueryParameter("duration", duration);
+ urlBuilder.addQueryParameter("category", category);
+ urlBuilder.addQueryParameter("limit", limit);
+ urlBuilder.addQueryParameter("offset", offset);
+ Timber.i("Url %s", urlBuilder.toString());
+ Request request = new Request.Builder()
+ .url(urlBuilder.toString())
+ .build();
+ return Observable.fromCallable(() -> {
+ Response response = okHttpClient.newCall(request).execute();
+ if (response != null && response.body() != null && response.isSuccessful()) {
+ String json = response.body().string();
+ if (json == null) {
+ return new LeaderboardResponse();
+ }
+ Timber.d("Response for leaderboard is %s", json);
+ try {
+ return gson.fromJson(json, LeaderboardResponse.class);
+ } catch (Exception e) {
+ return new LeaderboardResponse();
+ }
}
- }
- }
- }
- return 0;
- });
- }
-
- @NonNull
- public Single getWikidataEdits(String userName) {
- HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder();
- urlBuilder
- .addPathSegments("wikidataedits.py")
- .addQueryParameter("user", userName);
-
- if (ConfigUtils.isBetaFlavour()) {
- urlBuilder.addQueryParameter("labs", "commonswiki");
+ return new LeaderboardResponse();
+ });
}
- Request request = new Request.Builder()
- .url(urlBuilder.build())
- .build();
+ /**
+ * This method will update the leaderboard user avatar
+ *
+ * @param username username to update
+ * @param avatar url of the new avatar
+ * @return UpdateAvatarResponse object
+ */
+ @NonNull
+ public Single setAvatar(String username, String avatar) {
+ final String urlTemplate = wikiMediaTestToolforgeUrl
+ + UPDATE_AVATAR_END_POINT;
+ return Single.fromCallable(() -> {
+ String url = String.format(Locale.ENGLISH,
+ urlTemplate,
+ username,
+ avatar);
+ HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
+ urlBuilder.addQueryParameter("user", username);
+ urlBuilder.addQueryParameter("avatar", avatar);
+ Timber.i("Url %s", urlBuilder.toString());
+ Request request = new Request.Builder()
+ .url(urlBuilder.toString())
+ .build();
+ Response response = okHttpClient.newCall(request).execute();
+ if (response != null && response.body() != null && response.isSuccessful()) {
+ String json = response.body().string();
+ if (json == null) {
+ return null;
+ }
+ try {
+ return gson.fromJson(json, UpdateAvatarResponse.class);
+ } catch (Exception e) {
+ return new UpdateAvatarResponse();
+ }
+ }
+ return null;
+ });
+ }
- return Single.fromCallable(() -> {
- Response response = okHttpClient.newCall(request).execute();
- if (response != null &&
- response.isSuccessful() && response.body() != null) {
- String json = response.body().string();
- if (json == null) {
- return 0;
- }
- GetWikidataEditCountResponse countResponse = gson
- .fromJson(json, GetWikidataEditCountResponse.class);
- if (null != countResponse) {
- return countResponse.getWikidataEditCount();
- }
- }
- return 0;
- });
- }
+ @NonNull
+ public Single getUploadCount(String userName) {
+ HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder();
+ urlBuilder
+ .addPathSegments("uploadsbyuser.py")
+ .addQueryParameter("user", userName);
- /**
- * This takes userName as input, which is then used to fetch the feedback/achievements statistics
- * using OkHttp and JavaRx. This function return JSONObject
- *
- * @param userName MediaWiki user name
- * @return
- */
- public Single getAchievements(String userName) {
- final String fetchAchievementUrlTemplate =
- wikiMediaToolforgeUrl + (ConfigUtils.isBetaFlavour() ? "/feedback.py?labs=commonswiki"
- : "/feedback.py");
- return Single.fromCallable(() -> {
- String url = String.format(
- Locale.ENGLISH,
- fetchAchievementUrlTemplate,
- userName);
- HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
- urlBuilder.addQueryParameter("user", userName);
- Request request = new Request.Builder()
- .url(urlBuilder.toString())
- .build();
- Response response = okHttpClient.newCall(request).execute();
- if (response != null && response.body() != null && response.isSuccessful()) {
- String json = response.body().string();
- if (json == null) {
- return null;
- }
- Timber.d("Response for achievements is %s", json);
- try {
- return gson.fromJson(json, FeedbackResponse.class);
- } catch (Exception e) {
- return new FeedbackResponse(0, 0, 0, new FeaturedImages(0, 0), 0, "");
+ if (ConfigUtils.isBetaFlavour()) {
+ urlBuilder.addQueryParameter("labs", "commonswiki");
}
+ Request request = new Request.Builder()
+ .url(urlBuilder.build())
+ .build();
- }
- return null;
- });
- }
+ return Single.fromCallable(() -> {
+ Response response = okHttpClient.newCall(request).execute();
+ if (response != null && response.isSuccessful()) {
+ ResponseBody responseBody = response.body();
+ if (null != responseBody) {
+ String responseBodyString = responseBody.string().trim();
+ if (!TextUtils.isEmpty(responseBodyString)) {
+ try {
+ return Integer.parseInt(responseBodyString);
+ } catch (NumberFormatException e) {
+ Timber.e(e);
+ }
+ }
+ }
+ }
+ return 0;
+ });
+ }
- public Observable> getNearbyPlaces(LatLng cur, String language, double radius) throws IOException {
+ @NonNull
+ public Single getWikidataEdits(String userName) {
+ HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder();
+ urlBuilder
+ .addPathSegments("wikidataedits.py")
+ .addQueryParameter("user", userName);
+
+ if (ConfigUtils.isBetaFlavour()) {
+ urlBuilder.addQueryParameter("labs", "commonswiki");
+ }
+
+ Request request = new Request.Builder()
+ .url(urlBuilder.build())
+ .build();
+
+ return Single.fromCallable(() -> {
+ Response response = okHttpClient.newCall(request).execute();
+ if (response != null &&
+ response.isSuccessful() && response.body() != null) {
+ String json = response.body().string();
+ if (json == null) {
+ return 0;
+ }
+ GetWikidataEditCountResponse countResponse = gson
+ .fromJson(json, GetWikidataEditCountResponse.class);
+ if (null != countResponse) {
+ return countResponse.getWikidataEditCount();
+ }
+ }
+ return 0;
+ });
+ }
+
+ /**
+ * This takes userName as input, which is then used to fetch the feedback/achievements
+ * statistics using OkHttp and JavaRx. This function return JSONObject
+ *
+ * @param userName MediaWiki user name
+ * @return
+ */
+ public Single getAchievements(String userName) {
+ final String fetchAchievementUrlTemplate =
+ wikiMediaToolforgeUrl + (ConfigUtils.isBetaFlavour() ? "/feedback.py?labs=commonswiki"
+ : "/feedback.py");
+ return Single.fromCallable(() -> {
+ String url = String.format(
+ Locale.ENGLISH,
+ fetchAchievementUrlTemplate,
+ userName);
+ HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
+ urlBuilder.addQueryParameter("user", userName);
+ Request request = new Request.Builder()
+ .url(urlBuilder.toString())
+ .build();
+ Response response = okHttpClient.newCall(request).execute();
+ if (response != null && response.body() != null && response.isSuccessful()) {
+ String json = response.body().string();
+ if (json == null) {
+ return null;
+ }
+ Timber.d("Response for achievements is %s", json);
+ try {
+ return gson.fromJson(json, FeedbackResponse.class);
+ } catch (Exception e) {
+ return new FeedbackResponse(0, 0, 0, new FeaturedImages(0, 0), 0, "");
+ }
+
+
+ }
+ return null;
+ });
+ }
+
+ public Observable> getNearbyPlaces(LatLng cur, String language, double radius)
+ throws IOException {
String wikidataQuery = FileUtils.readFromResource("/queries/nearby_query.rq");
String query = wikidataQuery
- .replace("${RAD}", String.format(Locale.ROOT, "%.2f", radius))
- .replace("${LAT}", String.format(Locale.ROOT, "%.4f", cur.getLatitude()))
- .replace("${LONG}", String.format(Locale.ROOT, "%.4f", cur.getLongitude()))
- .replace("${LANG}", language);
+ .replace("${RAD}", String.format(Locale.ROOT, "%.2f", radius))
+ .replace("${LAT}", String.format(Locale.ROOT, "%.4f", cur.getLatitude()))
+ .replace("${LONG}", String.format(Locale.ROOT, "%.4f", cur.getLongitude()))
+ .replace("${LANG}", language);
- HttpUrl.Builder urlBuilder = HttpUrl
- .parse(sparqlQueryUrl)
- .newBuilder()
- .addQueryParameter("query", query)
- .addQueryParameter("format", "json");
+ HttpUrl.Builder urlBuilder = HttpUrl
+ .parse(sparqlQueryUrl)
+ .newBuilder()
+ .addQueryParameter("query", query)
+ .addQueryParameter("format", "json");
- Request request = new Request.Builder()
- .url(urlBuilder.build())
- .build();
+ Request request = new Request.Builder()
+ .url(urlBuilder.build())
+ .build();
- return Observable.fromCallable(() -> {
- Response response = okHttpClient.newCall(request).execute();
- if (response != null && response.body() != null && response.isSuccessful()) {
- String json = response.body().string();
- if (json == null) {
- return new ArrayList<>();
- }
- NearbyResponse nearbyResponse = gson.fromJson(json, NearbyResponse.class);
- List bindings = nearbyResponse.getResults().getBindings();
- List places = new ArrayList<>();
- for (NearbyResultItem item : bindings) {
- places.add(Place.from(item));
- }
- return places;
- }
- return new ArrayList<>();
- });
- }
+ return Observable.fromCallable(() -> {
+ Response response = okHttpClient.newCall(request).execute();
+ if (response != null && response.body() != null && response.isSuccessful()) {
+ String json = response.body().string();
+ if (json == null) {
+ return new ArrayList<>();
+ }
+ NearbyResponse nearbyResponse = gson.fromJson(json, NearbyResponse.class);
+ List bindings = nearbyResponse.getResults().getBindings();
+ List places = new ArrayList<>();
+ for (NearbyResultItem item : bindings) {
+ places.add(Place.from(item));
+ }
+ return places;
+ }
+ return new ArrayList<>();
+ });
+ }
- /**
- * Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example:
- * bridge -> suspended bridge, aqueduct, etc
- */
- public Single> getChildDepictions(String qid, int startPosition,
- int limit) throws IOException {
- return depictedItemsFrom(sparqlQuery(qid, startPosition, limit,"/queries/subclasses_query.rq"));
- }
+ /**
+ * Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example:
+ * bridge -> suspended bridge, aqueduct, etc
+ */
+ public Single> getChildDepictions(String qid, int startPosition,
+ int limit) throws IOException {
+ return depictedItemsFrom(
+ sparqlQuery(qid, startPosition, limit, "/queries/subclasses_query.rq"));
+ }
- /**
- * Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example:
- * bridge -> suspended bridge, aqueduct, etc
- */
- public Single> getParentDepictions(String qid, int startPosition,
- int limit) throws IOException {
- return depictedItemsFrom(sparqlQuery(qid, startPosition, limit,
- "/queries/parentclasses_query.rq"));
- }
+ /**
+ * Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example:
+ * bridge -> suspended bridge, aqueduct, etc
+ */
+ public Single> getParentDepictions(String qid, int startPosition,
+ int limit) throws IOException {
+ return depictedItemsFrom(sparqlQuery(qid, startPosition, limit,
+ "/queries/parentclasses_query.rq"));
+ }
- private Single> depictedItemsFrom(Request request) {
- return depictsClient.toDepictions(Single.fromCallable(() -> {
- try (ResponseBody body = okHttpClient.newCall(request).execute().body()) {
- return gson.fromJson(body.string(), SparqlResponse.class);
- }
- }).doOnError(Timber::e));
- }
+ private Single> depictedItemsFrom(Request request) {
+ return depictsClient.toDepictions(Single.fromCallable(() -> {
+ try (ResponseBody body = okHttpClient.newCall(request).execute().body()) {
+ return gson.fromJson(body.string(), SparqlResponse.class);
+ }
+ }).doOnError(Timber::e));
+ }
- @NotNull
- private Request sparqlQuery(String qid, int startPosition, int limit, String fileName) throws IOException {
- String query = FileUtils.readFromResource(fileName)
- .replace("${QID}", qid)
- .replace("${LANG}", "\"" + Locale.getDefault().getLanguage() + "\"")
- .replace("${LIMIT}",""+ limit)
- .replace("${OFFSET}",""+ startPosition);
- HttpUrl.Builder urlBuilder = HttpUrl
- .parse(sparqlQueryUrl)
- .newBuilder()
- .addQueryParameter("query", query)
- .addQueryParameter("format", "json");
- return new Request.Builder()
- .url(urlBuilder.build())
- .build();
- }
+ @NotNull
+ private Request sparqlQuery(String qid, int startPosition, int limit, String fileName)
+ throws IOException {
+ String query = FileUtils.readFromResource(fileName)
+ .replace("${QID}", qid)
+ .replace("${LANG}", "\"" + Locale.getDefault().getLanguage() + "\"")
+ .replace("${LIMIT}", "" + limit)
+ .replace("${OFFSET}", "" + startPosition);
+ HttpUrl.Builder urlBuilder = HttpUrl
+ .parse(sparqlQueryUrl)
+ .newBuilder()
+ .addQueryParameter("query", query)
+ .addQueryParameter("format", "json");
+ return new Request.Builder()
+ .url(urlBuilder.build())
+ .build();
+ }
- public Single getCampaigns() {
- return Single.fromCallable(() -> {
- Request request = new Request.Builder().url(campaignsUrl)
- .build();
- Response response = okHttpClient.newCall(request).execute();
- if (response != null && response.body() != null && response.isSuccessful()) {
- String json = response.body().string();
- if (json == null) {
- return null;
- }
- return gson.fromJson(json, CampaignResponseDTO.class);
- }
- return null;
- });
- }
+ public Single getCampaigns() {
+ return Single.fromCallable(() -> {
+ Request request = new Request.Builder().url(campaignsUrl)
+ .build();
+ Response response = okHttpClient.newCall(request).execute();
+ if (response != null && response.body() != null && response.isSuccessful()) {
+ String json = response.body().string();
+ if (json == null) {
+ return null;
+ }
+ return gson.fromJson(json, CampaignResponseDTO.class);
+ }
+ return null;
+ });
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/navtab/NavTab.java b/app/src/main/java/fr/free/nrw/commons/navtab/NavTab.java
index f9699931a..134b3c672 100644
--- a/app/src/main/java/fr/free/nrw/commons/navtab/NavTab.java
+++ b/app/src/main/java/fr/free/nrw/commons/navtab/NavTab.java
@@ -16,82 +16,81 @@ import org.wikipedia.model.EnumCodeMap;
import fr.free.nrw.commons.R;
-
public enum NavTab implements EnumCode {
- CONTRIBUTIONS(R.string.contributions_fragment, R.drawable.ic_baseline_person_24) {
+ CONTRIBUTIONS(R.string.contributions_fragment, R.drawable.ic_baseline_person_24) {
+ @NonNull
+ @Override
+ public Fragment newInstance() {
+ return ContributionsFragment.newInstance();
+ }
+ },
+ NEARBY(R.string.nearby_fragment, R.drawable.ic_location_on_black_24dp) {
+ @NonNull
+ @Override
+ public Fragment newInstance() {
+ return NearbyParentFragment.newInstance();
+ }
+ },
+ EXPLORE(R.string.navigation_item_explore, R.drawable.ic_globe) {
+ @NonNull
+ @Override
+ public Fragment newInstance() {
+ return ExploreFragment.newInstance();
+ }
+ },
+ FAVORITES(R.string.favorites, R.drawable.ic_round_star_border_24px) {
+ @NonNull
+ @Override
+ public Fragment newInstance() {
+ return BookmarkFragment.newInstance();
+ }
+ },
+ MORE(R.string.more, R.drawable.ic_menu_black_24dp) {
+ @NonNull
+ @Override
+ public Fragment newInstance() {
+ return null;
+ }
+ };
+
+ private static final EnumCodeMap MAP = new EnumCodeMap<>(NavTab.class);
+
+ @StringRes
+ private final int text;
+ @DrawableRes
+ private final int icon;
+
@NonNull
- @Override
- public Fragment newInstance() {
- return ContributionsFragment.newInstance();
+ public static NavTab of(int code) {
+ return MAP.get(code);
}
- },
- NEARBY(R.string.nearby_fragment, R.drawable.ic_location_on_black_24dp){
+
+ public static int size() {
+ return MAP.size();
+ }
+
+ @StringRes
+ public int text() {
+ return text;
+ }
+
+ @DrawableRes
+ public int icon() {
+ return icon;
+ }
+
@NonNull
+ public abstract Fragment newInstance();
+
@Override
- public Fragment newInstance() {
- return NearbyParentFragment.newInstance();
+ public int code() {
+ // This enumeration is not marshalled so tying declaration order to presentation order is
+ // convenient and consistent.
+ return ordinal();
}
- },
- EXPLORE(R.string.navigation_item_explore, R.drawable.ic_globe) {
- @NonNull
- @Override
- public Fragment newInstance() {
- return ExploreFragment.newInstance();
+
+ NavTab(@StringRes int text, @DrawableRes int icon) {
+ this.text = text;
+ this.icon = icon;
}
- },
- FAVORITES(R.string.favorites, R.drawable.ic_round_star_border_24px) {
- @NonNull
- @Override
- public Fragment newInstance() {
- return BookmarkFragment.newInstance();
- }
- },
- MORE(R.string.more, R.drawable.ic_menu_black_24dp) {
- @NonNull
- @Override
- public Fragment newInstance() {
- return null;
- }
- };
-
- private static final EnumCodeMap MAP = new EnumCodeMap<>(NavTab.class);
-
- @StringRes
- private final int text;
- @DrawableRes
- private final int icon;
-
- @NonNull
- public static NavTab of(int code) {
- return MAP.get(code);
- }
-
- public static int size() {
- return MAP.size();
- }
-
- @StringRes
- public int text() {
- return text;
- }
-
- @DrawableRes
- public int icon() {
- return icon;
- }
-
- @NonNull
- public abstract Fragment newInstance();
-
- @Override
- public int code() {
- // This enumeration is not marshalled so tying declaration order to presentation order is
- // convenient and consistent.
- return ordinal();
- }
-
- NavTab(@StringRes int text, @DrawableRes int icon) {
- this.text = text;
- this.icon = icon;
- }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/navtab/NavTabFragmentPagerAdapter.java b/app/src/main/java/fr/free/nrw/commons/navtab/NavTabFragmentPagerAdapter.java
index c725561e9..5384f2e01 100644
--- a/app/src/main/java/fr/free/nrw/commons/navtab/NavTabFragmentPagerAdapter.java
+++ b/app/src/main/java/fr/free/nrw/commons/navtab/NavTabFragmentPagerAdapter.java
@@ -8,28 +8,31 @@ import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
public class NavTabFragmentPagerAdapter extends FragmentPagerAdapter {
- private Fragment currentFragment;
- public NavTabFragmentPagerAdapter(FragmentManager mgr) {
- super(mgr);
- }
+ private Fragment currentFragment;
- @Nullable
- public Fragment getCurrentFragment() {
- return currentFragment;
- }
+ public NavTabFragmentPagerAdapter(FragmentManager mgr) {
+ super(mgr);
+ }
- @Override public Fragment getItem(int pos) {
- return NavTab.of(pos).newInstance();
- }
+ @Nullable
+ public Fragment getCurrentFragment() {
+ return currentFragment;
+ }
- @Override public int getCount() {
- return NavTab.size();
- }
+ @Override
+ public Fragment getItem(int pos) {
+ return NavTab.of(pos).newInstance();
+ }
- @Override
- public void setPrimaryItem(ViewGroup container, int position, Object object) {
- currentFragment = ((Fragment) object);
- super.setPrimaryItem(container, position, object);
- }
+ @Override
+ public int getCount() {
+ return NavTab.size();
+ }
+
+ @Override
+ public void setPrimaryItem(ViewGroup container, int position, Object object) {
+ currentFragment = ((Fragment) object);
+ super.setPrimaryItem(container, position, object);
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/navtab/NavTabLayout.java b/app/src/main/java/fr/free/nrw/commons/navtab/NavTabLayout.java
index b7cf63145..399cbc789 100644
--- a/app/src/main/java/fr/free/nrw/commons/navtab/NavTabLayout.java
+++ b/app/src/main/java/fr/free/nrw/commons/navtab/NavTabLayout.java
@@ -10,32 +10,32 @@ import fr.free.nrw.commons.contributions.MainActivity;
public class NavTabLayout extends BottomNavigationView {
- public NavTabLayout(Context context) {
- super(context);
- setTabViews();
- }
-
- public NavTabLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- setTabViews();
- }
-
- public NavTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- setTabViews();
- }
-
- private void setTabViews() {
- if (((MainActivity)getContext()).applicationKvStore.getBoolean("login_skipped") == true) {
- for (int i = 0; i < NavTabLoggedOut.size(); i++) {
- NavTabLoggedOut navTab = NavTabLoggedOut.of(i);
- getMenu().add(Menu.NONE, i, i, navTab.text()).setIcon(navTab.icon());
- }
- } else {
- for (int i = 0; i < NavTab.size(); i++) {
- NavTab navTab = NavTab.of(i);
- getMenu().add(Menu.NONE, i, i, navTab.text()).setIcon(navTab.icon());
- }
+ public NavTabLayout(Context context) {
+ super(context);
+ setTabViews();
+ }
+
+ public NavTabLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setTabViews();
+ }
+
+ public NavTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ setTabViews();
+ }
+
+ private void setTabViews() {
+ if (((MainActivity) getContext()).applicationKvStore.getBoolean("login_skipped") == true) {
+ for (int i = 0; i < NavTabLoggedOut.size(); i++) {
+ NavTabLoggedOut navTab = NavTabLoggedOut.of(i);
+ getMenu().add(Menu.NONE, i, i, navTab.text()).setIcon(navTab.icon());
+ }
+ } else {
+ for (int i = 0; i < NavTab.size(); i++) {
+ NavTab navTab = NavTab.of(i);
+ getMenu().add(Menu.NONE, i, i, navTab.text()).setIcon(navTab.icon());
+ }
+ }
}
- }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/navtab/NavTabLoggedOut.java b/app/src/main/java/fr/free/nrw/commons/navtab/NavTabLoggedOut.java
index 4f5324a9b..2ddf5e3e0 100644
--- a/app/src/main/java/fr/free/nrw/commons/navtab/NavTabLoggedOut.java
+++ b/app/src/main/java/fr/free/nrw/commons/navtab/NavTabLoggedOut.java
@@ -13,66 +13,67 @@ import org.wikipedia.model.EnumCodeMap;
public enum NavTabLoggedOut implements EnumCode {
- EXPLORE(R.string.navigation_item_explore, R.drawable.ic_globe) {
+ EXPLORE(R.string.navigation_item_explore, R.drawable.ic_globe) {
+ @NonNull
+ @Override
+ public Fragment newInstance() {
+ return ExploreFragment.newInstance();
+ }
+ },
+ FAVORITES(R.string.favorites, R.drawable.ic_round_star_border_24px) {
+ @NonNull
+ @Override
+ public Fragment newInstance() {
+ return BookmarkFragment.newInstance();
+ }
+ },
+ MORE(R.string.more, R.drawable.ic_menu_black_24dp) {
+ @NonNull
+ @Override
+ public Fragment newInstance() {
+ return null;
+ }
+ };
+
+ private static final EnumCodeMap MAP = new EnumCodeMap<>(
+ NavTabLoggedOut.class);
+
+ @StringRes
+ private final int text;
+ @DrawableRes
+ private final int icon;
+
@NonNull
- @Override
- public Fragment newInstance() {
- return ExploreFragment.newInstance();
+ public static NavTabLoggedOut of(int code) {
+ return MAP.get(code);
}
- },
- FAVORITES(R.string.favorites, R.drawable.ic_round_star_border_24px) {
+
+ public static int size() {
+ return MAP.size();
+ }
+
+ @StringRes
+ public int text() {
+ return text;
+ }
+
+ @DrawableRes
+ public int icon() {
+ return icon;
+ }
+
@NonNull
+ public abstract Fragment newInstance();
+
@Override
- public Fragment newInstance() {
- return BookmarkFragment.newInstance();
+ public int code() {
+ // This enumeration is not marshalled so tying declaration order to presentation order is
+ // convenient and consistent.
+ return ordinal();
}
- },
- MORE(R.string.more, R.drawable.ic_menu_black_24dp) {
- @NonNull
- @Override
- public Fragment newInstance() {
- return null;
+
+ NavTabLoggedOut(@StringRes int text, @DrawableRes int icon) {
+ this.text = text;
+ this.icon = icon;
}
- };
-
- private static final EnumCodeMap MAP = new EnumCodeMap<>(NavTabLoggedOut.class);
-
- @StringRes
- private final int text;
- @DrawableRes
- private final int icon;
-
- @NonNull
- public static NavTabLoggedOut of(int code) {
- return MAP.get(code);
- }
-
- public static int size() {
- return MAP.size();
- }
-
- @StringRes
- public int text() {
- return text;
- }
-
- @DrawableRes
- public int icon() {
- return icon;
- }
-
- @NonNull
- public abstract Fragment newInstance();
-
- @Override
- public int code() {
- // This enumeration is not marshalled so tying declaration order to presentation order is
- // convenient and consistent.
- return ordinal();
- }
-
- NavTabLoggedOut(@StringRes int text, @DrawableRes int icon) {
- this.text = text;
- this.icon = icon;
- }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/FileUtilsWrapper.java b/app/src/main/java/fr/free/nrw/commons/upload/FileUtilsWrapper.java
index d7f06155e..51bbd02bf 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/FileUtilsWrapper.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/FileUtilsWrapper.java
@@ -20,65 +20,65 @@ import timber.log.Timber;
@Singleton
public class FileUtilsWrapper {
- @Inject
- public FileUtilsWrapper() {
+ @Inject
+ public FileUtilsWrapper() {
- }
-
- public String getFileExt(String fileName) {
- return FileUtils.getFileExt(fileName);
- }
-
- public String getSHA1(InputStream is) {
- return FileUtils.getSHA1(is);
- }
-
- public FileInputStream getFileInputStream(String filePath) throws FileNotFoundException {
- return FileUtils.getFileInputStream(filePath);
- }
-
- public String getGeolocationOfFile(String filePath) {
- return FileUtils.getGeolocationOfFile(filePath);
- }
-
-
- /**
- * Takes a file as input and returns an Observable of files with the specified chunk size
- */
- public List getFileChunks(Context context, File file, final int chunkSize)
- throws IOException {
- final byte[] buffer = new byte[chunkSize];
-
- //try-with-resources to ensure closing stream
- try (final FileInputStream fis = new FileInputStream(file);
- final BufferedInputStream bis = new BufferedInputStream(fis)) {
- final List buffers = new ArrayList<>();
- int size;
- while ((size = bis.read(buffer)) > 0) {
- buffers.add(writeToFile(context, Arrays.copyOf(buffer, size), file.getName(),
- getFileExt(file.getName())));
- }
- return buffers;
}
- }
- /**
- * Create a temp file containing the passed byte data.
- */
- private File writeToFile(Context context, final byte[] data, final String fileName,
- String fileExtension)
- throws IOException {
- final File file = File.createTempFile(fileName, fileExtension, context.getCacheDir());
- try {
- if (!file.exists()) {
- file.createNewFile();
- }
- final FileOutputStream fos = new FileOutputStream(file);
- fos.write(data);
- fos.close();
- } catch (final Exception throwable) {
- Timber.e(throwable, "Failed to create file");
+ public String getFileExt(String fileName) {
+ return FileUtils.getFileExt(fileName);
+ }
+
+ public String getSHA1(InputStream is) {
+ return FileUtils.getSHA1(is);
+ }
+
+ public FileInputStream getFileInputStream(String filePath) throws FileNotFoundException {
+ return FileUtils.getFileInputStream(filePath);
+ }
+
+ public String getGeolocationOfFile(String filePath) {
+ return FileUtils.getGeolocationOfFile(filePath);
+ }
+
+
+ /**
+ * Takes a file as input and returns an Observable of files with the specified chunk size
+ */
+ public List getFileChunks(Context context, File file, final int chunkSize)
+ throws IOException {
+ final byte[] buffer = new byte[chunkSize];
+
+ //try-with-resources to ensure closing stream
+ try (final FileInputStream fis = new FileInputStream(file);
+ final BufferedInputStream bis = new BufferedInputStream(fis)) {
+ final List buffers = new ArrayList<>();
+ int size;
+ while ((size = bis.read(buffer)) > 0) {
+ buffers.add(writeToFile(context, Arrays.copyOf(buffer, size), file.getName(),
+ getFileExt(file.getName())));
+ }
+ return buffers;
+ }
+ }
+
+ /**
+ * Create a temp file containing the passed byte data.
+ */
+ private File writeToFile(Context context, final byte[] data, final String fileName,
+ String fileExtension)
+ throws IOException {
+ final File file = File.createTempFile(fileName, fileExtension, context.getCacheDir());
+ try {
+ if (!file.exists()) {
+ file.createNewFile();
+ }
+ final FileOutputStream fos = new FileOutputStream(file);
+ fos.write(data);
+ fos.close();
+ } catch (final Exception throwable) {
+ Timber.e(throwable, "Failed to create file");
+ }
+ return file;
}
- return file;
- }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.java b/app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.java
index e04afe979..1f31c1690 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.java
@@ -22,6 +22,7 @@ import timber.log.Timber;
*/
@Singleton
public class ImageProcessingService {
+
private final FileUtilsWrapper fileUtilsWrapper;
private final ImageUtilsWrapper imageUtilsWrapper;
private final ReadFBMD readFBMD;
@@ -30,9 +31,9 @@ public class ImageProcessingService {
@Inject
public ImageProcessingService(FileUtilsWrapper fileUtilsWrapper,
- ImageUtilsWrapper imageUtilsWrapper,
- ReadFBMD readFBMD, EXIFReader EXIFReader,
- MediaClient mediaClient, Context context) {
+ ImageUtilsWrapper imageUtilsWrapper,
+ ReadFBMD readFBMD, EXIFReader EXIFReader,
+ MediaClient mediaClient, Context context) {
this.fileUtilsWrapper = fileUtilsWrapper;
this.imageUtilsWrapper = imageUtilsWrapper;
this.readFBMD = readFBMD;
@@ -41,33 +42,34 @@ public class ImageProcessingService {
}
- /**
- * Check image quality before upload - checks duplicate image - checks dark image - checks
- * geolocation for image - check for valid title
- */
- Single validateImage(UploadItem uploadItem) {
- int currentImageQuality = uploadItem.getImageQuality();
- Timber.d("Current image quality is %d", currentImageQuality);
- if (currentImageQuality == ImageUtils.IMAGE_KEEP) {
- return Single.just(ImageUtils.IMAGE_OK);
- }
- Timber.d("Checking the validity of image");
- String filePath = uploadItem.getMediaUri().getPath();
-
- return Single.zip(
- checkDuplicateImage(filePath),
- checkImageGeoLocation(uploadItem.getPlace(), filePath),
- checkDarkImage(filePath),
- validateItemTitle(uploadItem),
- checkFBMD(filePath),
- checkEXIF(filePath),
- (duplicateImage, wrongGeoLocation, darkImage, itemTitle, fbmd, exif) -> {
- Timber.d("duplicate: %d, geo: %d, dark: %d, title: %d" + "fbmd:" + fbmd + "exif:" + exif,
- duplicateImage, wrongGeoLocation, darkImage, itemTitle);
- return duplicateImage | wrongGeoLocation | darkImage | itemTitle | fbmd | exif;
+ /**
+ * Check image quality before upload - checks duplicate image - checks dark image - checks
+ * geolocation for image - check for valid title
+ */
+ Single validateImage(UploadItem uploadItem) {
+ int currentImageQuality = uploadItem.getImageQuality();
+ Timber.d("Current image quality is %d", currentImageQuality);
+ if (currentImageQuality == ImageUtils.IMAGE_KEEP) {
+ return Single.just(ImageUtils.IMAGE_OK);
}
- );
- }
+ Timber.d("Checking the validity of image");
+ String filePath = uploadItem.getMediaUri().getPath();
+
+ return Single.zip(
+ checkDuplicateImage(filePath),
+ checkImageGeoLocation(uploadItem.getPlace(), filePath),
+ checkDarkImage(filePath),
+ validateItemTitle(uploadItem),
+ checkFBMD(filePath),
+ checkEXIF(filePath),
+ (duplicateImage, wrongGeoLocation, darkImage, itemTitle, fbmd, exif) -> {
+ Timber.d("duplicate: %d, geo: %d, dark: %d, title: %d" + "fbmd:" + fbmd + "exif:"
+ + exif,
+ duplicateImage, wrongGeoLocation, darkImage, itemTitle);
+ return duplicateImage | wrongGeoLocation | darkImage | itemTitle | fbmd | exif;
+ }
+ );
+ }
/**
* We want to discourage users from uploading images to Commons that were taken from Facebook.
@@ -79,10 +81,10 @@ public class ImageProcessingService {
}
/**
- * We try to minimize uploads from the Commons app that might be copyright violations.
- * If an image does not have any Exif metadata, then it was likely downloaded from the internet,
- * and is probably not an original work by the user. We detect these kinds of images by looking
- * for the presence of some basic Exif metadata.
+ * We try to minimize uploads from the Commons app that might be copyright violations. If an
+ * image does not have any Exif metadata, then it was likely downloaded from the internet, and
+ * is probably not an original work by the user. We detect these kinds of images by looking for
+ * the presence of some basic Exif metadata.
*/
private Single checkEXIF(String filepath) {
return EXIFReader.processMetadata(filepath);
@@ -90,9 +92,7 @@ public class ImageProcessingService {
/**
- * Checks item caption
- * - empty caption
- * - existing caption
+ * Checks item caption - empty caption - existing caption
*
* @param uploadItem
* @return
@@ -105,11 +105,11 @@ public class ImageProcessingService {
}
return mediaClient.checkPageExistsUsingTitle("File:" + uploadItem.getFileName())
- .map(doesFileExist -> {
- Timber.d("Result for valid title is %s", doesFileExist);
- return doesFileExist ? FILE_NAME_EXISTS : IMAGE_OK;
- })
- .subscribeOn(Schedulers.io());
+ .map(doesFileExist -> {
+ Timber.d("Result for valid title is %s", doesFileExist);
+ return doesFileExist ? FILE_NAME_EXISTS : IMAGE_OK;
+ })
+ .subscribeOn(Schedulers.io());
}
/**
@@ -121,13 +121,13 @@ public class ImageProcessingService {
private Single checkDuplicateImage(String filePath) {
Timber.d("Checking for duplicate image %s", filePath);
return Single.fromCallable(() -> fileUtilsWrapper.getFileInputStream(filePath))
- .map(fileUtilsWrapper::getSHA1)
- .flatMap(mediaClient::checkFileExistsUsingSha)
- .map(b -> {
- Timber.d("Result for duplicate image %s", b);
- return b ? ImageUtils.IMAGE_DUPLICATE : ImageUtils.IMAGE_OK;
- })
- .subscribeOn(Schedulers.io());
+ .map(fileUtilsWrapper::getSHA1)
+ .flatMap(mediaClient::checkFileExistsUsingSha)
+ .map(b -> {
+ Timber.d("Result for duplicate image %s", b);
+ return b ? ImageUtils.IMAGE_DUPLICATE : ImageUtils.IMAGE_OK;
+ })
+ .subscribeOn(Schedulers.io());
}
/**
@@ -142,8 +142,8 @@ public class ImageProcessingService {
}
/**
- * Checks for image geolocation
- * returns IMAGE_OK if the place is null or if the file doesn't contain a geolocation
+ * Checks for image geolocation returns IMAGE_OK if the place is null or if the file doesn't
+ * contain a geolocation
*
* @param filePath file to be checked
* @return IMAGE_GEOLOCATION_DIFFERENT or IMAGE_OK
@@ -154,14 +154,15 @@ public class ImageProcessingService {
return Single.just(ImageUtils.IMAGE_OK);
}
return Single.fromCallable(() -> filePath)
- .map(fileUtilsWrapper::getGeolocationOfFile)
- .flatMap(geoLocation -> {
- if (StringUtils.isBlank(geoLocation)) {
- return Single.just(ImageUtils.IMAGE_OK);
- }
- return imageUtilsWrapper.checkImageGeolocationIsDifferent(geoLocation, place.getLocation());
- })
- .subscribeOn(Schedulers.io());
+ .map(fileUtilsWrapper::getGeolocationOfFile)
+ .flatMap(geoLocation -> {
+ if (StringUtils.isBlank(geoLocation)) {
+ return Single.just(ImageUtils.IMAGE_OK);
+ }
+ return imageUtilsWrapper
+ .checkImageGeolocationIsDifferent(geoLocation, place.getLocation());
+ })
+ .subscribeOn(Schedulers.io());
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/PageContentsCreator.java b/app/src/main/java/fr/free/nrw/commons/upload/PageContentsCreator.java
index ddb629979..e344cf55d 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/PageContentsCreator.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/PageContentsCreator.java
@@ -16,96 +16,96 @@ 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}}";
+ //{{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";
+ //2009-01-09 → 9 January 2009
+ private static final String TEMPLATE_DATA_OTHER_SOURCE = "%s";
- private final Context context;
+ private final Context context;
- @Inject
- public PageContentsCreator(Context context) {
- this.context = context;
- }
-
- public String createFrom(Contribution contribution) {
- StringBuilder buffer = new StringBuilder();
- final Media media = contribution.getMedia();
- buffer
- .append("== {{int:filedesc}} ==\n")
- .append("{{Information\n")
- .append("|description=").append(media.getFallbackDescription()).append("\n")
- .append("|source=").append("{{own}}\n")
- .append("|author=[[User:").append(media.getAuthor()).append("|")
- .append(media.getAuthor()).append("]]\n");
-
- String templatizedCreatedDate = getTemplatizedCreatedDate(
- contribution.getDateCreated(), contribution.getDateCreatedSource());
- if (!StringUtils.isBlank(templatizedCreatedDate)) {
- buffer.append("|date=").append(templatizedCreatedDate);
+ @Inject
+ public PageContentsCreator(Context context) {
+ this.context = context;
}
- buffer.append("}}").append("\n");
+ public String createFrom(Contribution contribution) {
+ StringBuilder buffer = new StringBuilder();
+ final Media media = contribution.getMedia();
+ buffer
+ .append("== {{int:filedesc}} ==\n")
+ .append("{{Information\n")
+ .append("|description=").append(media.getFallbackDescription()).append("\n")
+ .append("|source=").append("{{own}}\n")
+ .append("|author=[[User:").append(media.getAuthor()).append("|")
+ .append(media.getAuthor()).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");
+ 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(media.getLicense())).append("\n\n")
+ .append("{{Uploaded from Mobile|platform=Android|version=")
+ .append(ConfigUtils.getVersionNameWithSha(context)).append("}}\n");
+ final List categories = media.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();
}
- buffer.append("== {{int:license-header}} ==\n")
- .append(licenseTemplateFor(media.getLicense())).append("\n\n")
- .append("{{Uploaded from Mobile|platform=Android|version=")
- .append(ConfigUtils.getVersionNameWithSha(context)).append("}}\n");
- final List categories = media.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}}";
+ /**
+ * 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 "";
}
- throw new RuntimeException("Unrecognized license value: " + license);
- }
+ 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);
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/ReadFBMD.java b/app/src/main/java/fr/free/nrw/commons/upload/ReadFBMD.java
index e98ab9ec5..586f9fc24 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/ReadFBMD.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/ReadFBMD.java
@@ -15,34 +15,34 @@ import javax.inject.Singleton;
@Singleton
public class ReadFBMD {
- @Inject
- public ReadFBMD() {
- }
+ @Inject
+ public ReadFBMD() {
+ }
- public Single processMetadata(String path) {
- return Single.fromCallable(() -> {
- try {
- int psBlockOffset;
- int fbmdOffset;
+ public Single processMetadata(String path) {
+ return Single.fromCallable(() -> {
+ try {
+ int psBlockOffset;
+ int fbmdOffset;
- try (FileInputStream fs = new FileInputStream(path)) {
- byte[] bytes = new byte[4096];
- fs.read(bytes);
- fs.close();
- String fileStr = new String(bytes);
- psBlockOffset = fileStr.indexOf("8BIM");
- fbmdOffset = fileStr.indexOf("FBMD");
- }
+ try (FileInputStream fs = new FileInputStream(path)) {
+ byte[] bytes = new byte[4096];
+ fs.read(bytes);
+ fs.close();
+ String fileStr = new String(bytes);
+ psBlockOffset = fileStr.indexOf("8BIM");
+ fbmdOffset = fileStr.indexOf("FBMD");
+ }
- if (psBlockOffset > 0 && fbmdOffset > 0
- && fbmdOffset > psBlockOffset && fbmdOffset - psBlockOffset < 0x80) {
- return ImageUtils.FILE_FBMD;
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- return ImageUtils.IMAGE_OK;
- });
- }
+ if (psBlockOffset > 0 && fbmdOffset > 0
+ && fbmdOffset > psBlockOffset && fbmdOffset - psBlockOffset < 0x80) {
+ return ImageUtils.FILE_FBMD;
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return ImageUtils.IMAGE_OK;
+ });
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadClient.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadClient.java
index 2212e2b5b..b363caf99 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadClient.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadClient.java
@@ -35,199 +35,204 @@ import timber.log.Timber;
@Singleton
public class UploadClient {
- private final int CHUNK_SIZE = 512 * 1024; // 512 KB
+ private final int CHUNK_SIZE = 512 * 1024; // 512 KB
- //This is maximum duration for which a stash is persisted on MediaWiki
- // https://www.mediawiki.org/wiki/Manual:$wgUploadStashMaxAge
- private final int MAX_CHUNK_AGE = 6 * 3600 * 1000; // 6 hours
+ //This is maximum duration for which a stash is persisted on MediaWiki
+ // https://www.mediawiki.org/wiki/Manual:$wgUploadStashMaxAge
+ private final int MAX_CHUNK_AGE = 6 * 3600 * 1000; // 6 hours
- private final UploadInterface uploadInterface;
- private final CsrfTokenClient csrfTokenClient;
- private final PageContentsCreator pageContentsCreator;
- private final FileUtilsWrapper fileUtilsWrapper;
- private final Gson gson;
+ private final UploadInterface uploadInterface;
+ private final CsrfTokenClient csrfTokenClient;
+ private final PageContentsCreator pageContentsCreator;
+ private final FileUtilsWrapper fileUtilsWrapper;
+ private final Gson gson;
- private final CompositeDisposable compositeDisposable = new CompositeDisposable();
+ private final CompositeDisposable compositeDisposable = new CompositeDisposable();
- @Inject
- public UploadClient(final UploadInterface uploadInterface,
- @Named(NAMED_COMMONS_CSRF) final CsrfTokenClient csrfTokenClient,
- final PageContentsCreator pageContentsCreator,
- final FileUtilsWrapper fileUtilsWrapper, final Gson gson) {
- this.uploadInterface = uploadInterface;
- this.csrfTokenClient = csrfTokenClient;
- this.pageContentsCreator = pageContentsCreator;
- this.fileUtilsWrapper = fileUtilsWrapper;
- this.gson = gson;
- }
-
- /**
- * Upload file to stash in chunks of specified size. Uploading files in chunks will make handling
- * of large files easier. Also, it will be useful in supporting pause/resume of uploads
- */
- public Observable uploadFileToStash(
- final Context context, final String filename, final Contribution contribution,
- final NotificationUpdateProgressListener notificationUpdater) throws IOException {
- if (contribution.getChunkInfo() != null
- && contribution.getChunkInfo().getTotalChunks() == contribution.getChunkInfo()
- .getIndexOfNextChunkToUpload()) {
- return Observable.just(new StashUploadResult(StashUploadState.SUCCESS,
- contribution.getChunkInfo().getUploadResult().getFilekey()));
+ @Inject
+ public UploadClient(final UploadInterface uploadInterface,
+ @Named(NAMED_COMMONS_CSRF) final CsrfTokenClient csrfTokenClient,
+ final PageContentsCreator pageContentsCreator,
+ final FileUtilsWrapper fileUtilsWrapper, final Gson gson) {
+ this.uploadInterface = uploadInterface;
+ this.csrfTokenClient = csrfTokenClient;
+ this.pageContentsCreator = pageContentsCreator;
+ this.fileUtilsWrapper = fileUtilsWrapper;
+ this.gson = gson;
}
- CommonsApplication.pauseUploads.put(contribution.getPageId(), false);
+ /**
+ * Upload file to stash in chunks of specified size. Uploading files in chunks will make
+ * handling of large files easier. Also, it will be useful in supporting pause/resume of
+ * uploads
+ */
+ public Observable uploadFileToStash(
+ final Context context, final String filename, final Contribution contribution,
+ final NotificationUpdateProgressListener notificationUpdater) throws IOException {
+ if (contribution.getChunkInfo() != null
+ && contribution.getChunkInfo().getTotalChunks() == contribution.getChunkInfo()
+ .getIndexOfNextChunkToUpload()) {
+ return Observable.just(new StashUploadResult(StashUploadState.SUCCESS,
+ contribution.getChunkInfo().getUploadResult().getFilekey()));
+ }
- final File file = new File(contribution.getLocalUri().getPath());
- final List fileChunks = fileUtilsWrapper.getFileChunks(context, file, CHUNK_SIZE);
+ CommonsApplication.pauseUploads.put(contribution.getPageId(), false);
- final int totalChunks = fileChunks.size();
+ final File file = new File(contribution.getLocalUri().getPath());
+ final List fileChunks = fileUtilsWrapper.getFileChunks(context, file, CHUNK_SIZE);
- final MediaType mediaType = MediaType
- .parse(FileUtils.getMimeType(context, Uri.parse(file.getPath())));
+ final int totalChunks = fileChunks.size();
- final AtomicReference chunkInfo = new AtomicReference<>();
- if (isStashValid(contribution)) {
- chunkInfo.set(contribution.getChunkInfo());
+ final MediaType mediaType = MediaType
+ .parse(FileUtils.getMimeType(context, Uri.parse(file.getPath())));
- Timber.d("Chunk: Next Chunk: %s, Total Chunks: %s",
- contribution.getChunkInfo().getIndexOfNextChunkToUpload(),
- contribution.getChunkInfo().getTotalChunks());
- }
+ final AtomicReference chunkInfo = new AtomicReference<>();
+ if (isStashValid(contribution)) {
+ chunkInfo.set(contribution.getChunkInfo());
- final AtomicInteger index = new AtomicInteger();
- final AtomicBoolean failures = new AtomicBoolean();
+ Timber.d("Chunk: Next Chunk: %s, Total Chunks: %s",
+ contribution.getChunkInfo().getIndexOfNextChunkToUpload(),
+ contribution.getChunkInfo().getTotalChunks());
+ }
- compositeDisposable.add(Observable.fromIterable(fileChunks).forEach(chunkFile -> {
- if (CommonsApplication.pauseUploads.get(contribution.getPageId()) || failures.get()) {
- return;
- }
+ final AtomicInteger index = new AtomicInteger();
+ final AtomicBoolean failures = new AtomicBoolean();
- if (chunkInfo.get() != null && index.get() < chunkInfo.get().getIndexOfNextChunkToUpload()) {
- index.incrementAndGet();
- Timber.d("Chunk: Increment and return: %s", index.get());
- return;
- }
- index.getAndIncrement();
- final int offset =
- chunkInfo.get() != null ? chunkInfo.get().getUploadResult().getOffset() : 0;
-
- Timber.d("Chunk: Sending Chunk number: %s, offset: %s", index.get(), offset);
- final String filekey =
- chunkInfo.get() != null ? chunkInfo.get().getUploadResult().getFilekey() : null;
-
- final RequestBody requestBody = RequestBody
- .create(mediaType, chunkFile);
- final CountingRequestBody countingRequestBody = new CountingRequestBody(requestBody,
- notificationUpdater::onProgress, offset,
- file.length());
-
- compositeDisposable.add(uploadChunkToStash(filename,
- file.length(),
- offset,
- filekey,
- countingRequestBody).subscribe(uploadResult -> {
- Timber.d("Chunk: Received Chunk number: %s, offset: %s", index.get(),
- uploadResult.getOffset());
- chunkInfo.set(
- new ChunkInfo(uploadResult, index.get(), totalChunks));
- notificationUpdater.onChunkUploaded(contribution, chunkInfo.get());
- }, throwable -> {
- Timber.e(throwable, "Received error in chunk upload");
- failures.set(true);
- }));
- }));
-
- if (CommonsApplication.pauseUploads.get(contribution.getPageId())) {
- Timber.d("Upload stash paused %s", contribution.getPageId());
- return Observable.just(new StashUploadResult(StashUploadState.PAUSED, null));
- } else if (failures.get()) {
- Timber.d("Upload stash contains failures %s", contribution.getPageId());
- return Observable.just(new StashUploadResult(StashUploadState.FAILED, null));
- } else if (chunkInfo.get() != null) {
- Timber.d("Upload stash success %s", contribution.getPageId());
- return Observable.just(new StashUploadResult(StashUploadState.SUCCESS,
- chunkInfo.get().getUploadResult().getFilekey()));
- } else {
- Timber.d("Upload stash failed %s", contribution.getPageId());
- return Observable.just(new StashUploadResult(StashUploadState.FAILED, null));
- }
- }
-
- /**
- * Stash is valid for 6 hours. This function checks the validity of stash
- * @param contribution
- * @return
- */
- private boolean isStashValid(Contribution contribution) {
- return contribution.getChunkInfo() != null &&
- contribution.getDateModified()
- .after(new Date(System.currentTimeMillis() - MAX_CHUNK_AGE));
- }
-
- /**
- * Uploads a file chunk to stash
- *
- * @param filename The name of the file being uploaded
- * @param fileSize The total size of the file
- * @param offset The offset returned by the previous chunk upload
- * @param fileKey The filekey returned by the previous chunk upload
- * @param countingRequestBody Request body with chunk file
- * @return
- */
- Observable uploadChunkToStash(final String filename,
- final long fileSize,
- final long offset,
- final String fileKey,
- final CountingRequestBody countingRequestBody) {
- final MultipartBody.Part filePart;
- try {
- filePart = MultipartBody.Part
- .createFormData("chunk", URLEncoder.encode(filename, "utf-8"), countingRequestBody);
-
- return uploadInterface.uploadFileToStash(toRequestBody(filename),
- toRequestBody(String.valueOf(fileSize)),
- toRequestBody(String.valueOf(offset)),
- toRequestBody(fileKey),
- toRequestBody(csrfTokenClient.getTokenBlocking()),
- filePart)
- .map(UploadResponse::getUpload);
- } catch (final Throwable throwable) {
- Timber.e(throwable, "Failed to upload chunk to stash");
- return Observable.error(throwable);
- }
- }
-
- /**
- * Converts string value to request body
- */
- @Nullable
- private RequestBody toRequestBody(@Nullable final String value) {
- return value == null ? null : RequestBody.create(okhttp3.MultipartBody.FORM, value);
- }
-
-
- public Observable uploadFileFromStash(
- final Contribution contribution,
- final String uniqueFileName,
- final String fileKey) {
- try {
- return uploadInterface
- .uploadFileFromStash(csrfTokenClient.getTokenBlocking(),
- pageContentsCreator.createFrom(contribution),
- CommonsApplication.DEFAULT_EDIT_SUMMARY,
- uniqueFileName,
- fileKey).map(uploadResponse -> {
- UploadResponse uploadResult = gson.fromJson(uploadResponse, UploadResponse.class);
- if (uploadResult.getUpload() == null) {
- final MwException exception = gson.fromJson(uploadResponse, MwException.class);
- Timber.e(exception, "Error in uploading file from stash");
- throw new RuntimeException(exception.getErrorCode());
+ compositeDisposable.add(Observable.fromIterable(fileChunks).forEach(chunkFile -> {
+ if (CommonsApplication.pauseUploads.get(contribution.getPageId()) || failures.get()) {
+ return;
}
- return uploadResult.getUpload();
- });
- } catch (final Throwable throwable) {
- Timber.e(throwable, "Exception occurred in uploading file from stash");
- return Observable.error(throwable);
+
+ if (chunkInfo.get() != null && index.get() < chunkInfo.get()
+ .getIndexOfNextChunkToUpload()) {
+ index.incrementAndGet();
+ Timber.d("Chunk: Increment and return: %s", index.get());
+ return;
+ }
+ index.getAndIncrement();
+ final int offset =
+ chunkInfo.get() != null ? chunkInfo.get().getUploadResult().getOffset() : 0;
+
+ Timber.d("Chunk: Sending Chunk number: %s, offset: %s", index.get(), offset);
+ final String filekey =
+ chunkInfo.get() != null ? chunkInfo.get().getUploadResult().getFilekey() : null;
+
+ final RequestBody requestBody = RequestBody
+ .create(mediaType, chunkFile);
+ final CountingRequestBody countingRequestBody = new CountingRequestBody(requestBody,
+ notificationUpdater::onProgress, offset,
+ file.length());
+
+ compositeDisposable.add(uploadChunkToStash(filename,
+ file.length(),
+ offset,
+ filekey,
+ countingRequestBody).subscribe(uploadResult -> {
+ Timber.d("Chunk: Received Chunk number: %s, offset: %s", index.get(),
+ uploadResult.getOffset());
+ chunkInfo.set(
+ new ChunkInfo(uploadResult, index.get(), totalChunks));
+ notificationUpdater.onChunkUploaded(contribution, chunkInfo.get());
+ }, throwable -> {
+ Timber.e(throwable, "Received error in chunk upload");
+ failures.set(true);
+ }));
+ }));
+
+ if (CommonsApplication.pauseUploads.get(contribution.getPageId())) {
+ Timber.d("Upload stash paused %s", contribution.getPageId());
+ return Observable.just(new StashUploadResult(StashUploadState.PAUSED, null));
+ } else if (failures.get()) {
+ Timber.d("Upload stash contains failures %s", contribution.getPageId());
+ return Observable.just(new StashUploadResult(StashUploadState.FAILED, null));
+ } else if (chunkInfo.get() != null) {
+ Timber.d("Upload stash success %s", contribution.getPageId());
+ return Observable.just(new StashUploadResult(StashUploadState.SUCCESS,
+ chunkInfo.get().getUploadResult().getFilekey()));
+ } else {
+ Timber.d("Upload stash failed %s", contribution.getPageId());
+ return Observable.just(new StashUploadResult(StashUploadState.FAILED, null));
+ }
+ }
+
+ /**
+ * Stash is valid for 6 hours. This function checks the validity of stash
+ *
+ * @param contribution
+ * @return
+ */
+ private boolean isStashValid(Contribution contribution) {
+ return contribution.getChunkInfo() != null &&
+ contribution.getDateModified()
+ .after(new Date(System.currentTimeMillis() - MAX_CHUNK_AGE));
+ }
+
+ /**
+ * Uploads a file chunk to stash
+ *
+ * @param filename The name of the file being uploaded
+ * @param fileSize The total size of the file
+ * @param offset The offset returned by the previous chunk upload
+ * @param fileKey The filekey returned by the previous chunk upload
+ * @param countingRequestBody Request body with chunk file
+ * @return
+ */
+ Observable uploadChunkToStash(final String filename,
+ final long fileSize,
+ final long offset,
+ final String fileKey,
+ final CountingRequestBody countingRequestBody) {
+ final MultipartBody.Part filePart;
+ try {
+ filePart = MultipartBody.Part
+ .createFormData("chunk", URLEncoder.encode(filename, "utf-8"), countingRequestBody);
+
+ return uploadInterface.uploadFileToStash(toRequestBody(filename),
+ toRequestBody(String.valueOf(fileSize)),
+ toRequestBody(String.valueOf(offset)),
+ toRequestBody(fileKey),
+ toRequestBody(csrfTokenClient.getTokenBlocking()),
+ filePart)
+ .map(UploadResponse::getUpload);
+ } catch (final Throwable throwable) {
+ Timber.e(throwable, "Failed to upload chunk to stash");
+ return Observable.error(throwable);
+ }
+ }
+
+ /**
+ * Converts string value to request body
+ */
+ @Nullable
+ private RequestBody toRequestBody(@Nullable final String value) {
+ return value == null ? null : RequestBody.create(okhttp3.MultipartBody.FORM, value);
+ }
+
+
+ public Observable uploadFileFromStash(
+ final Contribution contribution,
+ final String uniqueFileName,
+ final String fileKey) {
+ try {
+ return uploadInterface
+ .uploadFileFromStash(csrfTokenClient.getTokenBlocking(),
+ pageContentsCreator.createFrom(contribution),
+ CommonsApplication.DEFAULT_EDIT_SUMMARY,
+ uniqueFileName,
+ fileKey).map(uploadResponse -> {
+ UploadResponse uploadResult = gson
+ .fromJson(uploadResponse, UploadResponse.class);
+ if (uploadResult.getUpload() == null) {
+ final MwException exception = gson
+ .fromJson(uploadResponse, MwException.class);
+ Timber.e(exception, "Error in uploading file from stash");
+ throw new RuntimeException(exception.getErrorCode());
+ }
+ return uploadResult.getUpload();
+ });
+ } catch (final Throwable throwable) {
+ Timber.e(throwable, "Exception occurred in uploading file from stash");
+ return Observable.error(throwable);
+ }
}
- }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.java
index 0d76ba57c..0487fd87f 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.java
@@ -14,110 +14,111 @@ import java.util.List;
public class UploadItem {
- private final Uri mediaUri;
- private final String mimeType;
- private ImageCoordinates gpsCoords;
- private List uploadMediaDetails;
- private Place place;
- private final long createdTimestamp;
- private final String createdTimestampSource;
- private final BehaviorSubject imageQuality;
- private boolean hasInvalidLocation;
+ private final Uri mediaUri;
+ private final String mimeType;
+ private ImageCoordinates gpsCoords;
+ private List uploadMediaDetails;
+ private Place place;
+ private final long createdTimestamp;
+ private final String createdTimestampSource;
+ private final BehaviorSubject imageQuality;
+ private boolean hasInvalidLocation;
- @SuppressLint("CheckResult")
- UploadItem(final Uri mediaUri,
- final String mimeType,
- final ImageCoordinates gpsCoords,
- final Place place,
- final long createdTimestamp,
- final String createdTimestampSource) {
- this.createdTimestampSource = createdTimestampSource;
- uploadMediaDetails = new ArrayList<>(Collections.singletonList(new UploadMediaDetail()));
- this.place = place;
- this.mediaUri = mediaUri;
- this.mimeType = mimeType;
- this.gpsCoords = gpsCoords;
- this.createdTimestamp = createdTimestamp;
- imageQuality = BehaviorSubject.createDefault(ImageUtils.IMAGE_WAIT);
- }
-
- public String getCreatedTimestampSource() {
- return createdTimestampSource;
- }
-
- public ImageCoordinates getGpsCoords() {
- return gpsCoords;
- }
-
- public List getUploadMediaDetails() {
- return uploadMediaDetails;
- }
-
- public long getCreatedTimestamp() {
- return createdTimestamp;
- }
-
- public Uri getMediaUri() {
- return mediaUri;
- }
-
- public int getImageQuality() {
- return imageQuality.getValue();
- }
-
- public void setImageQuality(final int imageQuality) {
- this.imageQuality.onNext(imageQuality);
- }
-
- /**
- * Sets the corresponding place to the uploadItem
- * @param place geolocated Wikidata item
- */
- public void setPlace(Place place) {
- this.place = place;
- }
-
- public Place getPlace() {
- return place;
- }
-
- public void setMediaDetails(final List uploadMediaDetails) {
- this.uploadMediaDetails = uploadMediaDetails;
- }
-
- @Override
- public boolean equals(@Nullable final Object obj) {
- if (!(obj instanceof UploadItem)) {
- return false;
+ @SuppressLint("CheckResult")
+ UploadItem(final Uri mediaUri,
+ final String mimeType,
+ final ImageCoordinates gpsCoords,
+ final Place place,
+ final long createdTimestamp,
+ final String createdTimestampSource) {
+ this.createdTimestampSource = createdTimestampSource;
+ uploadMediaDetails = new ArrayList<>(Collections.singletonList(new UploadMediaDetail()));
+ this.place = place;
+ this.mediaUri = mediaUri;
+ this.mimeType = mimeType;
+ this.gpsCoords = gpsCoords;
+ this.createdTimestamp = createdTimestamp;
+ imageQuality = BehaviorSubject.createDefault(ImageUtils.IMAGE_WAIT);
}
- return mediaUri.toString().contains(((UploadItem) (obj)).mediaUri.toString());
- }
+ public String getCreatedTimestampSource() {
+ return createdTimestampSource;
+ }
- @Override
- public int hashCode() {
- return mediaUri.hashCode();
- }
+ public ImageCoordinates getGpsCoords() {
+ return gpsCoords;
+ }
- /**
- * Choose a filename for the media. Currently, the caption is used as a filename. If several
- * languages have been entered, the first language is used.
- */
- public String getFileName() {
- return Utils.fixExtension(uploadMediaDetails.get(0).getCaptionText(),
- MimeTypeMapWrapper.getExtensionFromMimeType(mimeType));
- }
+ public List getUploadMediaDetails() {
+ return uploadMediaDetails;
+ }
- public void setGpsCoords(final ImageCoordinates gpsCoords) {
- this.gpsCoords = gpsCoords;
- }
+ public long getCreatedTimestamp() {
+ return createdTimestamp;
+ }
- public void setHasInvalidLocation(boolean hasInvalidLocation) {
- this.hasInvalidLocation=hasInvalidLocation;
- }
+ public Uri getMediaUri() {
+ return mediaUri;
+ }
- public boolean hasInvalidLocation() {
- return hasInvalidLocation;
- }
+ public int getImageQuality() {
+ return imageQuality.getValue();
+ }
+
+ public void setImageQuality(final int imageQuality) {
+ this.imageQuality.onNext(imageQuality);
+ }
+
+ /**
+ * Sets the corresponding place to the uploadItem
+ *
+ * @param place geolocated Wikidata item
+ */
+ public void setPlace(Place place) {
+ this.place = place;
+ }
+
+ public Place getPlace() {
+ return place;
+ }
+
+ public void setMediaDetails(final List uploadMediaDetails) {
+ this.uploadMediaDetails = uploadMediaDetails;
+ }
+
+ @Override
+ public boolean equals(@Nullable final Object obj) {
+ if (!(obj instanceof UploadItem)) {
+ return false;
+ }
+ return mediaUri.toString().contains(((UploadItem) (obj)).mediaUri.toString());
+
+ }
+
+ @Override
+ public int hashCode() {
+ return mediaUri.hashCode();
+ }
+
+ /**
+ * Choose a filename for the media. Currently, the caption is used as a filename. If several
+ * languages have been entered, the first language is used.
+ */
+ public String getFileName() {
+ return Utils.fixExtension(uploadMediaDetails.get(0).getCaptionText(),
+ MimeTypeMapWrapper.getExtensionFromMimeType(mimeType));
+ }
+
+ public void setGpsCoords(final ImageCoordinates gpsCoords) {
+ this.gpsCoords = gpsCoords;
+ }
+
+ public void setHasInvalidLocation(boolean hasInvalidLocation) {
+ this.hasInvalidLocation = hasInvalidLocation;
+ }
+
+ public boolean hasInvalidLocation() {
+ return hasInvalidLocation;
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/ActivityUtils.java b/app/src/main/java/fr/free/nrw/commons/utils/ActivityUtils.java
index 0b33ed566..4806585dc 100644
--- a/app/src/main/java/fr/free/nrw/commons/utils/ActivityUtils.java
+++ b/app/src/main/java/fr/free/nrw/commons/utils/ActivityUtils.java
@@ -4,11 +4,12 @@ import android.content.Context;
import android.content.Intent;
public class ActivityUtils {
- public static void startActivityWithFlags(Context context, Class cls, int... flags) {
- Intent intent = new Intent(context, cls);
- for (int flag: flags) {
- intent.addFlags(flag);
+
+ public static void startActivityWithFlags(Context context, Class cls, int... flags) {
+ Intent intent = new Intent(context, cls);
+ for (int flag : flags) {
+ intent.addFlags(flag);
+ }
+ context.startActivity(intent);
}
- context.startActivity(intent);
- }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/ImageUtilsWrapper.java b/app/src/main/java/fr/free/nrw/commons/utils/ImageUtilsWrapper.java
index bb40ee1cf..634a73ad2 100644
--- a/app/src/main/java/fr/free/nrw/commons/utils/ImageUtilsWrapper.java
+++ b/app/src/main/java/fr/free/nrw/commons/utils/ImageUtilsWrapper.java
@@ -9,22 +9,22 @@ import javax.inject.Singleton;
@Singleton
public class ImageUtilsWrapper {
- @Inject
- public ImageUtilsWrapper() {
+ @Inject
+ public ImageUtilsWrapper() {
- }
+ }
- public Single checkIfImageIsTooDark(String bitmapPath) {
- return Single.fromCallable(() -> ImageUtils.checkIfImageIsTooDark(bitmapPath))
- .subscribeOn(Schedulers.computation());
- }
+ public Single checkIfImageIsTooDark(String bitmapPath) {
+ return Single.fromCallable(() -> ImageUtils.checkIfImageIsTooDark(bitmapPath))
+ .subscribeOn(Schedulers.computation());
+ }
- public Single checkImageGeolocationIsDifferent(String geolocationOfFileString,
- LatLng latLng) {
- return Single.fromCallable(
- () -> ImageUtils.checkImageGeolocationIsDifferent(geolocationOfFileString, latLng))
- .subscribeOn(Schedulers.computation())
- .map(isDifferent -> isDifferent ? ImageUtils.IMAGE_GEOLOCATION_DIFFERENT
- : ImageUtils.IMAGE_OK);
- }
+ public Single checkImageGeolocationIsDifferent(String geolocationOfFileString,
+ LatLng latLng) {
+ return Single.fromCallable(
+ () -> ImageUtils.checkImageGeolocationIsDifferent(geolocationOfFileString, latLng))
+ .subscribeOn(Schedulers.computation())
+ .map(isDifferent -> isDifferent ? ImageUtils.IMAGE_GEOLOCATION_DIFFERENT
+ : ImageUtils.IMAGE_OK);
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/WikiBaseClient.java b/app/src/main/java/fr/free/nrw/commons/wikidata/WikiBaseClient.java
index 0208c3d57..0ffce9ee4 100644
--- a/app/src/main/java/fr/free/nrw/commons/wikidata/WikiBaseClient.java
+++ b/app/src/main/java/fr/free/nrw/commons/wikidata/WikiBaseClient.java
@@ -19,43 +19,44 @@ import timber.log.Timber;
@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 postEditEntity(String fileEntityId, String data) {
- return csrfToken()
- .switchMap(editToken -> wikiBaseInterface.postEditEntity(fileEntityId, editToken, data)
- .map(response -> (response.getSuccessVal() == 1)));
- }
+ public Observable postEditEntity(String fileEntityId, String data) {
+ return csrfToken()
+ .switchMap(editToken -> wikiBaseInterface.postEditEntity(fileEntityId, editToken, data)
+ .map(response -> (response.getSuccessVal() == 1)));
+ }
- public Observable getFileEntityId(UploadResult uploadResult) {
- return wikiBaseInterface.getFileEntityId(uploadResult.createCanonicalFileName())
- .map(response -> (long) (response.query().pages().get(0).pageId()));
- }
+ public Observable getFileEntityId(UploadResult uploadResult) {
+ return wikiBaseInterface.getFileEntityId(uploadResult.createCanonicalFileName())
+ .map(response -> (long) (response.query().pages().get(0).pageId()));
+ }
- public Observable addLabelstoWikidata(long fileEntityId,
- String languageCode, String captionValue) {
- return csrfToken()
- .switchMap(editToken -> wikiBaseInterface
- .addLabelstoWikidata(PAGE_ID_PREFIX + fileEntityId, editToken, languageCode, captionValue));
+ public Observable addLabelstoWikidata(long fileEntityId,
+ String languageCode, String captionValue) {
+ return csrfToken()
+ .switchMap(editToken -> wikiBaseInterface
+ .addLabelstoWikidata(PAGE_ID_PREFIX + fileEntityId, editToken, languageCode,
+ captionValue));
- }
+ }
- private Observable csrfToken() {
- return Observable.fromCallable(() -> {
- try {
- return csrfTokenClient.getTokenBlocking();
- } catch (Throwable throwable) {
- Timber.e(throwable);
- return "";
- }
- });
- }
+ private Observable csrfToken() {
+ return Observable.fromCallable(() -> {
+ try {
+ return csrfTokenClient.getTokenBlocking();
+ } catch (Throwable throwable) {
+ Timber.e(throwable);
+ return "";
+ }
+ });
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataClient.java b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataClient.java
index ebee708e9..a476d5b40 100644
--- a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataClient.java
+++ b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataClient.java
@@ -15,32 +15,33 @@ import org.wikipedia.wikidata.Statement_partial;
public class WikidataClient {
- private final WikidataInterface wikidataInterface;
- private final Gson gson;
+ private final WikidataInterface wikidataInterface;
+ private final Gson gson;
- @Inject
- public WikidataClient(WikidataInterface wikidataInterface, final Gson gson) {
- this.wikidataInterface = wikidataInterface;
- this.gson = gson;
- }
+ @Inject
+ public WikidataClient(WikidataInterface wikidataInterface, final Gson gson) {
+ this.wikidataInterface = wikidataInterface;
+ this.gson = gson;
+ }
- /**
- * Create wikidata claim to add P18 value
- *
- * @return revisionID of the edit
- */
- Observable setClaim(Statement_partial claim, String tags) {
- return getCsrfToken()
- .flatMap(csrfToken -> wikidataInterface.postSetClaim(gson.toJson(claim), tags, csrfToken))
- .map(mwPostResponse -> mwPostResponse.getPageinfo().getLastrevid());
- }
+ /**
+ * Create wikidata claim to add P18 value
+ *
+ * @return revisionID of the edit
+ */
+ Observable setClaim(Statement_partial claim, String tags) {
+ return getCsrfToken()
+ .flatMap(
+ csrfToken -> wikidataInterface.postSetClaim(gson.toJson(claim), tags, csrfToken))
+ .map(mwPostResponse -> mwPostResponse.getPageinfo().getLastrevid());
+ }
- /**
- * Get csrf token for wikidata edit
- */
- @NotNull
- private Observable getCsrfToken() {
- return wikidataInterface.getCsrfToken()
- .map(mwQueryResponse -> mwQueryResponse.query().csrfToken());
- }
+ /**
+ * Get csrf token for wikidata edit
+ */
+ @NotNull
+ private Observable getCsrfToken() {
+ return wikidataInterface.getCsrfToken()
+ .map(mwQueryResponse -> mwQueryResponse.query().csrfToken());
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.java b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.java
index e37dd7cf1..e283c98a8 100644
--- a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.java
+++ b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.java
@@ -44,169 +44,172 @@ import timber.log.Timber;
@Singleton
public class WikidataEditService {
- public static final String COMMONS_APP_TAG = "wikimedia-commons-app";
+ public static final String COMMONS_APP_TAG = "wikimedia-commons-app";
- private final Context context;
- private final WikidataEditListener wikidataEditListener;
- private final JsonKvStore directKvStore;
- private final WikiBaseClient wikiBaseClient;
- private final WikidataClient wikidataClient;
- private final Gson gson;
+ private final Context context;
+ private final WikidataEditListener wikidataEditListener;
+ private final JsonKvStore directKvStore;
+ private final WikiBaseClient wikiBaseClient;
+ private final WikidataClient wikidataClient;
+ private final Gson gson;
- @Inject
- public WikidataEditService(final Context context,
- final WikidataEditListener wikidataEditListener,
- @Named("default_preferences") final JsonKvStore directKvStore,
- final WikiBaseClient wikiBaseClient,
- final WikidataClient wikidataClient, final Gson gson) {
- this.context = context;
- this.wikidataEditListener = wikidataEditListener;
- this.directKvStore = directKvStore;
- this.wikiBaseClient = wikiBaseClient;
- this.wikidataClient = wikidataClient;
- this.gson = gson;
- }
-
- /**
- * 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 addDepictsProperty(final String fileEntityId,
- final WikidataItem depictedItem) {
-
- final EditClaim data = editClaim(
- ConfigUtils.isBetaFlavour() ? "Q10" // Wikipedia:Sandbox (Q10)
- : depictedItem.getId()
- );
-
- return wikiBaseClient.postEditEntity(PAGE_ID_PREFIX + fileEntityId, gson.toJson(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());
- }
-
- private EditClaim editClaim(final String entityId) {
- return EditClaim.from(entityId, WikidataProperties.DEPICTS.getPropertyName());
- }
-
- /**
- * Show a success toast when the edit is made successfully
- */
- 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);
- }
-
- /**
- * Adds label to Wikidata using the fileEntityId and the edit token, obtained from
- * csrfTokenClient
- *
- * @param fileEntityId
- * @return
- */
-
- @SuppressLint("CheckResult")
- private Observable 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 Long createClaim(@Nullable final WikidataPlace wikidataPlace, final String fileName, final
- Map captions) {
- 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 null;
- }
- return addImageAndMediaLegends(wikidataPlace, fileName, captions);
- }
-
- public Long addImageAndMediaLegends(final WikidataItem wikidataItem, final String fileName,
- final Map captions) {
- final Snak_partial p18 = new Snak_partial("value", WikidataProperties.IMAGE.getPropertyName(),
- new ValueString(fileName.replace("File:", "")));
-
- final List snaks = new ArrayList<>();
- for (final Map.Entry entry : captions.entrySet()) {
- snaks.add(new Snak_partial("value",
- WikidataProperties.MEDIA_LEGENDS.getPropertyName(), new DataValue.MonoLingualText(
- new WikiBaseMonolingualTextValue(entry.getValue(), entry.getKey()))));
+ @Inject
+ public WikidataEditService(final Context context,
+ final WikidataEditListener wikidataEditListener,
+ @Named("default_preferences") final JsonKvStore directKvStore,
+ final WikiBaseClient wikiBaseClient,
+ final WikidataClient wikidataClient, final Gson gson) {
+ this.context = context;
+ this.wikidataEditListener = wikidataEditListener;
+ this.directKvStore = directKvStore;
+ this.wikiBaseClient = wikiBaseClient;
+ this.wikidataClient = wikidataClient;
+ this.gson = gson;
}
- final String id = wikidataItem.getId() + "$" + UUID.randomUUID().toString();
- final Statement_partial claim = new Statement_partial(p18, "statement", "normal", id,
- Collections.singletonMap(WikidataProperties.MEDIA_LEGENDS.getPropertyName(), snaks),
- Arrays.asList(WikidataProperties.MEDIA_LEGENDS.getPropertyName()));
+ /**
+ * 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 addDepictsProperty(final String fileEntityId,
+ final WikidataItem depictedItem) {
- return wikidataClient.setClaim(claim, COMMONS_APP_TAG).blockingSingle();
- }
-
- public void handleImageClaimResult(final WikidataItem wikidataItem, final Long 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 Observable addDepictionsAndCaptions(final UploadResult uploadResult, final 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));
- })
- .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();
- }
- }
+ final EditClaim data = editClaim(
+ ConfigUtils.isBetaFlavour() ? "Q10" // Wikipedia:Sandbox (Q10)
+ : depictedItem.getId()
);
- }
- private Observable captionEdits(Contribution contribution, Long fileEntityId) {
- return Observable.fromIterable(contribution.getMedia().getCaptions().entrySet())
- .concatMap(entry -> addCaption(fileEntityId, entry.getKey(), entry.getValue()));
- }
+ return wikiBaseClient.postEditEntity(PAGE_ID_PREFIX + fileEntityId, gson.toJson(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());
+ }
- private Observable depictionEdits(Contribution contribution, Long fileEntityId) {
- return Observable.fromIterable(contribution.getDepictedItems())
- .concatMap(wikidataItem -> addDepictsProperty(fileEntityId.toString(), wikidataItem));
- }
+ private EditClaim editClaim(final String entityId) {
+ return EditClaim.from(entityId, WikidataProperties.DEPICTS.getPropertyName());
+ }
+
+ /**
+ * Show a success toast when the edit is made successfully
+ */
+ 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);
+ }
+
+ /**
+ * Adds label to Wikidata using the fileEntityId and the edit token, obtained from
+ * csrfTokenClient
+ *
+ * @param fileEntityId
+ * @return
+ */
+
+ @SuppressLint("CheckResult")
+ private Observable 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 Long createClaim(@Nullable final WikidataPlace wikidataPlace, final String fileName,
+ final Map captions) {
+ 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 null;
+ }
+ return addImageAndMediaLegends(wikidataPlace, fileName, captions);
+ }
+
+ public Long addImageAndMediaLegends(final WikidataItem wikidataItem, final String fileName,
+ final Map captions) {
+ final Snak_partial p18 = new Snak_partial("value",
+ WikidataProperties.IMAGE.getPropertyName(),
+ new ValueString(fileName.replace("File:", "")));
+
+ final List snaks = new ArrayList<>();
+ for (final Map.Entry entry : captions.entrySet()) {
+ snaks.add(new Snak_partial("value",
+ WikidataProperties.MEDIA_LEGENDS.getPropertyName(), new DataValue.MonoLingualText(
+ new WikiBaseMonolingualTextValue(entry.getValue(), entry.getKey()))));
+ }
+
+ final String id = wikidataItem.getId() + "$" + UUID.randomUUID().toString();
+ final Statement_partial claim = new Statement_partial(p18, "statement", "normal", id,
+ Collections.singletonMap(WikidataProperties.MEDIA_LEGENDS.getPropertyName(), snaks),
+ Arrays.asList(WikidataProperties.MEDIA_LEGENDS.getPropertyName()));
+
+ return wikidataClient.setClaim(claim, COMMONS_APP_TAG).blockingSingle();
+ }
+
+ public void handleImageClaimResult(final WikidataItem wikidataItem, final Long 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 Observable addDepictionsAndCaptions(final UploadResult uploadResult,
+ final 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));
+ })
+ .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();
+ }
+ }
+ );
+ }
+
+ private Observable captionEdits(Contribution contribution, Long fileEntityId) {
+ return Observable.fromIterable(contribution.getMedia().getCaptions().entrySet())
+ .concatMap(entry -> addCaption(fileEntityId, entry.getKey(), entry.getValue()));
+ }
+
+ private Observable depictionEdits(Contribution contribution, Long fileEntityId) {
+ return Observable.fromIterable(contribution.getDepictedItems())
+ .concatMap(wikidataItem -> addDepictsProperty(fileEntityId.toString(), wikidataItem));
+ }
}
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/ShadowActionBar.java b/app/src/test/kotlin/fr/free/nrw/commons/ShadowActionBar.java
index 26cec296b..a12858688 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/ShadowActionBar.java
+++ b/app/src/test/kotlin/fr/free/nrw/commons/ShadowActionBar.java
@@ -8,14 +8,14 @@ import org.robolectric.annotation.Implements;
@Implements(ActionBar.class)
public class ShadowActionBar {
- private boolean showHomeAsUp;
+ private boolean showHomeAsUp;
- public boolean getShowHomeAsUp() {
- return showHomeAsUp;
- }
+ public boolean getShowHomeAsUp() {
+ return showHomeAsUp;
+ }
- @Implementation
- void setDisplayHomeAsUpEnabled(final boolean showHomeAsUp) {
- this.showHomeAsUp = showHomeAsUp;
- }
+ @Implementation
+ void setDisplayHomeAsUpEnabled(final boolean showHomeAsUp) {
+ this.showHomeAsUp = showHomeAsUp;
+ }
}
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/TestAppAdapter.java b/app/src/test/kotlin/fr/free/nrw/commons/TestAppAdapter.java
index 39bcc515a..1b61a6fb7 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/TestAppAdapter.java
+++ b/app/src/test/kotlin/fr/free/nrw/commons/TestAppAdapter.java
@@ -12,60 +12,60 @@ import org.wikipedia.login.LoginResult;
public class TestAppAdapter extends AppAdapter {
- @Override
- public String getMediaWikiBaseUrl() {
- return Service.WIKIPEDIA_URL;
- }
+ @Override
+ public String getMediaWikiBaseUrl() {
+ return Service.WIKIPEDIA_URL;
+ }
- @Override
- public String getRestbaseUriFormat() {
- return "%1$s://%2$s/api/rest_v1/";
- }
+ @Override
+ public String getRestbaseUriFormat() {
+ return "%1$s://%2$s/api/rest_v1/";
+ }
- @Override
- public OkHttpClient getOkHttpClient(@NonNull WikiSite wikiSite) {
- return new OkHttpClient.Builder()
- .addInterceptor(new UnsuccessfulResponseInterceptor())
- .addInterceptor(new TestStubInterceptor())
- .build();
- }
+ @Override
+ public OkHttpClient getOkHttpClient(@NonNull WikiSite wikiSite) {
+ return new OkHttpClient.Builder()
+ .addInterceptor(new UnsuccessfulResponseInterceptor())
+ .addInterceptor(new TestStubInterceptor())
+ .build();
+ }
- @Override
- public int getDesiredLeadImageDp() {
- return 0;
- }
+ @Override
+ public int getDesiredLeadImageDp() {
+ return 0;
+ }
- @Override
- public boolean isLoggedIn() {
- return false;
- }
+ @Override
+ public boolean isLoggedIn() {
+ return false;
+ }
- @Override
- public String getUserName() {
- return null;
- }
+ @Override
+ public String getUserName() {
+ return null;
+ }
- @Override
- public String getPassword() {
- return null;
- }
+ @Override
+ public String getPassword() {
+ return null;
+ }
- @Override
- public void updateAccount(@NonNull LoginResult result) {
- }
+ @Override
+ public void updateAccount(@NonNull LoginResult result) {
+ }
- @Override
- public SharedPreferenceCookieManager getCookies() {
- return null;
- }
+ @Override
+ public SharedPreferenceCookieManager getCookies() {
+ return null;
+ }
- @Override
- public void setCookies(@NonNull SharedPreferenceCookieManager cookies) {
- }
+ @Override
+ public void setCookies(@NonNull SharedPreferenceCookieManager cookies) {
+ }
- @Override
- public boolean logErrorsInsteadOfCrashing() {
- return false;
- }
+ @Override
+ public boolean logErrorsInsteadOfCrashing() {
+ return false;
+ }
}