Fix - No Precise Error Message After Error Due to Password Change (#5643)

* initial commit

* Fix No Precise Message After Clicking Review Buttons

* Fix For ThanksClient

* Fix For Depictions

* Fix For Categories

* Fix For Description & Coordinates

* Fix For Description & Coordinates

* Fix For Description & Coordinates

* Fix For Mark as Read notifications

* resolve conflicts

* fix merge conflicts
This commit is contained in:
Shashank Kumar 2024-04-01 13:53:00 +05:30 committed by GitHub
parent e56de2c343
commit 3d1efecb55
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 361 additions and 110 deletions

View file

@ -30,9 +30,13 @@ import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
import fr.free.nrw.commons.CameraPosition; import fr.free.nrw.commons.CameraPosition;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.auth.csrf.CsrfTokenClient;
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException;
import fr.free.nrw.commons.coordinates.CoordinateEditHelper; import fr.free.nrw.commons.coordinates.CoordinateEditHelper;
import fr.free.nrw.commons.filepicker.Constants; import fr.free.nrw.commons.filepicker.Constants;
import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.kvstore.JsonKvStore;
@ -141,6 +145,8 @@ public class LocationPickerActivity extends BaseActivity implements
@Inject @Inject
LocationServiceManager locationManager; LocationServiceManager locationManager;
@Inject
SessionManager sessionManager;
/** /**
* Constants * Constants
@ -404,13 +410,29 @@ public class LocationPickerActivity extends BaseActivity implements
if (media == null) { if (media == null) {
return; return;
} }
compositeDisposable.add(coordinateEditHelper.makeCoordinatesEdit(getApplicationContext(),media,
Latitude, Longitude, Accuracy) try {
.subscribeOn(Schedulers.io()) compositeDisposable.add(
.observeOn(AndroidSchedulers.mainThread()) coordinateEditHelper.makeCoordinatesEdit(getApplicationContext(), media,
.subscribe(s -> { Latitude, Longitude, Accuracy)
Timber.d("Coordinates are added."); .subscribeOn(Schedulers.io())
})); .observeOn(AndroidSchedulers.mainThread())
.subscribe(s -> {
Timber.d("Coordinates are added.");
}));
} catch (Exception e) {
if (e.getLocalizedMessage().equals(CsrfTokenClient.ANONYMOUS_TOKEN_MESSAGE)) {
final String username = sessionManager.getUserName();
final CommonsApplication.BaseLogoutListener logoutListener = new CommonsApplication.BaseLogoutListener(
this,
getString(R.string.invalid_login_message),
username
);
CommonsApplication.getInstance().clearApplicationData(
this, logoutListener);
}
}
} }
/** /**

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.actions package fr.free.nrw.commons.actions
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.Single
import fr.free.nrw.commons.auth.csrf.CsrfTokenClient import fr.free.nrw.commons.auth.csrf.CsrfTokenClient
@ -28,7 +29,11 @@ class PageEditClient(
pageEditInterface.postEdit(pageTitle, summary, text, csrfTokenClient.getTokenBlocking()) pageEditInterface.postEdit(pageTitle, summary, text, csrfTokenClient.getTokenBlocking())
.map { editResponse -> editResponse.edit()!!.editSucceeded() } .map { editResponse -> editResponse.edit()!!.editSucceeded() }
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
Observable.just(false) if (throwable is InvalidLoginTokenException) {
throw throwable
} else {
Observable.just(false)
}
} }
} }
@ -44,7 +49,11 @@ class PageEditClient(
pageEditInterface.postAppendEdit(pageTitle, summary, appendText, csrfTokenClient.getTokenBlocking()) pageEditInterface.postAppendEdit(pageTitle, summary, appendText, csrfTokenClient.getTokenBlocking())
.map { editResponse -> editResponse.edit()!!.editSucceeded() } .map { editResponse -> editResponse.edit()!!.editSucceeded() }
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
Observable.just(false) if (throwable is InvalidLoginTokenException) {
throw throwable
} else {
Observable.just(false)
}
} }
} }
@ -58,12 +67,17 @@ class PageEditClient(
fun prependEdit(pageTitle: String, prependText: String, summary: String): Observable<Boolean> { fun prependEdit(pageTitle: String, prependText: String, summary: String): Observable<Boolean> {
return try { return try {
pageEditInterface.postPrependEdit(pageTitle, summary, prependText, csrfTokenClient.getTokenBlocking()) pageEditInterface.postPrependEdit(pageTitle, summary, prependText, csrfTokenClient.getTokenBlocking())
.map { editResponse -> editResponse.edit()!!.editSucceeded() } .map { editResponse -> editResponse.edit()?.editSucceeded() ?: false }
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
Observable.just(false) if (throwable is InvalidLoginTokenException) {
throw throwable
} else {
Observable.just(false)
}
} }
} }
/** /**
* Set new labels to Wikibase server of commons * Set new labels to Wikibase server of commons
* @param summary Edit summary * @param summary Edit summary
@ -79,7 +93,11 @@ class PageEditClient(
value, csrfTokenClient.getTokenBlocking() value, csrfTokenClient.getTokenBlocking()
).map { it.success } ).map { it.success }
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
Observable.just(0) if (throwable is InvalidLoginTokenException) {
throw throwable
} else {
Observable.just(0)
}
} }
} }
@ -93,4 +111,4 @@ class PageEditClient(
it.query()?.pages()?.get(0)?.revisions()?.get(0)?.content() it.query()?.pages()?.get(0)?.revisions()?.get(0)?.content()
} }
} }
} }

View file

@ -4,6 +4,8 @@ import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF import fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF
import io.reactivex.Observable import io.reactivex.Observable
import fr.free.nrw.commons.auth.csrf.CsrfTokenClient import fr.free.nrw.commons.auth.csrf.CsrfTokenClient
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException
import fr.free.nrw.commons.auth.login.LoginFailedException
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Named import javax.inject.Named
import javax.inject.Singleton import javax.inject.Singleton
@ -32,8 +34,15 @@ class ThanksClient @Inject constructor(
).map { ).map {
mwThankPostResponse -> mwThankPostResponse.result?.success == 1 mwThankPostResponse -> mwThankPostResponse.result?.success == 1
} }
} catch (throwable: Throwable) { }
Observable.just(false) catch (throwable: Throwable) {
if (throwable is InvalidLoginTokenException) {
Observable.error(throwable)
}
else {
Observable.just(false)
}
} }
} }
}
}

View file

@ -48,16 +48,19 @@ class CsrfTokenClient(
token = response.body()!!.query()!!.csrfToken()!! token = response.body()!!.query()!!.csrfToken()!!
if (sessionManager.isUserLoggedIn && token == ANON_TOKEN) { if (sessionManager.isUserLoggedIn && token == ANON_TOKEN) {
throw RuntimeException("App believes we're logged in, but got anonymous token.") throw InvalidLoginTokenException(ANONYMOUS_TOKEN_MESSAGE)
} }
break break
} catch (t: Throwable) { } catch (e: LoginFailedException) {
throw InvalidLoginTokenException(ANONYMOUS_TOKEN_MESSAGE)
}
catch (t: Throwable) {
Timber.w(t) Timber.w(t)
} }
} }
if (token.isEmpty() || token == ANON_TOKEN) { if (token.isEmpty() || token == ANON_TOKEN) {
throw IOException(INVALID_TOKEN_ERROR_MESSAGE) throw InvalidLoginTokenException(ANONYMOUS_TOKEN_MESSAGE)
} }
return token return token
} }
@ -68,7 +71,7 @@ class CsrfTokenClient(
override fun success(token: String?) { override fun success(token: String?) {
if (sessionManager.isUserLoggedIn && token == ANON_TOKEN) { if (sessionManager.isUserLoggedIn && token == ANON_TOKEN) {
retryWithLogin(cb) { retryWithLogin(cb) {
RuntimeException("App believes we're logged in, but got anonymous token.") InvalidLoginTokenException(ANONYMOUS_TOKEN_MESSAGE)
} }
} else { } else {
cb.success(token) cb.success(token)
@ -161,5 +164,8 @@ class CsrfTokenClient(
private const val MAX_RETRIES = 1 private const val MAX_RETRIES = 1
private const val MAX_RETRIES_OF_LOGIN_BLOCKING = 2 private const val MAX_RETRIES_OF_LOGIN_BLOCKING = 2
const val INVALID_TOKEN_ERROR_MESSAGE = "Invalid token, or login failure." const val INVALID_TOKEN_ERROR_MESSAGE = "Invalid token, or login failure."
const val ANONYMOUS_TOKEN_MESSAGE = "App believes we're logged in, but got anonymous token."
} }
} }
class InvalidLoginTokenException(message: String) : Exception(message)

View file

@ -13,6 +13,7 @@ import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.actions.PageEditClient; import fr.free.nrw.commons.actions.PageEditClient;
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException;
import fr.free.nrw.commons.notification.NotificationHelper; import fr.free.nrw.commons.notification.NotificationHelper;
import fr.free.nrw.commons.review.ReviewController; import fr.free.nrw.commons.review.ReviewController;
import fr.free.nrw.commons.utils.ViewUtilWrapper; import fr.free.nrw.commons.utils.ViewUtilWrapper;
@ -66,7 +67,13 @@ public class DeleteHelper {
return delete(media, reason) return delete(media, reason)
.flatMapSingle(result -> Single.just(showDeletionNotification(context, media, result))) .flatMapSingle(result -> Single.just(showDeletionNotification(context, media, result)))
.firstOrError(); .firstOrError()
.onErrorResumeNext(throwable -> {
if (throwable instanceof InvalidLoginTokenException) {
return Single.error(throwable);
}
return Single.error(throwable);
});
} }
/** /**
@ -104,22 +111,30 @@ public class DeleteHelper {
} }
return pageEditClient.prependEdit(media.getFilename(), fileDeleteString + "\n", summary) return pageEditClient.prependEdit(media.getFilename(), fileDeleteString + "\n", summary)
.flatMap(result -> { .onErrorResumeNext(throwable -> {
if (result) { if (throwable instanceof InvalidLoginTokenException) {
return pageEditClient.edit("Commons:Deletion_requests/" + media.getFilename(), subpageString + "\n", summary); return Observable.error(throwable);
} }
throw new RuntimeException("Failed to nominate for deletion"); return Observable.error(throwable);
}).flatMap(result -> { })
if (result) { .flatMap(result -> {
return pageEditClient.appendEdit("Commons:Deletion_requests/" + date, logPageString + "\n", summary); if (result) {
} return pageEditClient.edit("Commons:Deletion_requests/" + media.getFilename(), subpageString + "\n", summary);
throw new RuntimeException("Failed to nominate for deletion"); }
}).flatMap(result -> { return Observable.error(new RuntimeException("Failed to nominate for deletion"));
if (result) { })
return pageEditClient.appendEdit("User_Talk:" + creator, userPageString + "\n", summary); .flatMap(result -> {
} if (result) {
throw new RuntimeException("Failed to nominate for deletion"); return pageEditClient.appendEdit("Commons:Deletion_requests/" + date, logPageString + "\n", summary);
}); }
return Observable.error(new RuntimeException("Failed to nominate for deletion"));
})
.flatMap(result -> {
if (result) {
return pageEditClient.appendEdit("User_Talk:" + creator, userPageString + "\n", summary);
}
return Observable.error(new RuntimeException("Failed to nominate for deletion"));
});
} }
private boolean showDeletionNotification(Context context, Media media, boolean result) { private boolean showDeletionNotification(Context context, Media media, boolean result) {
@ -226,14 +241,15 @@ public class DeleteHelper {
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(aBoolean -> { .subscribe(aBoolean -> {
if (aBoolean) { reviewCallback.onSuccess();
reviewCallback.onSuccess(); }, throwable -> {
if (throwable instanceof InvalidLoginTokenException) {
reviewCallback.onTokenException((InvalidLoginTokenException) throwable);
} else { } else {
reviewCallback.onFailure(); reviewCallback.onFailure();
} }
reviewCallback.enableButtons(); reviewCallback.enableButtons();
}); });
}); });
alert.setNegativeButton(context.getString(R.string.cancel), (dialog, which) -> reviewCallback.onFailure()); alert.setNegativeButton(context.getString(R.string.cancel), (dialog, which) -> reviewCallback.onFailure());
d = alert.create(); d = alert.create();

View file

@ -49,6 +49,7 @@ import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.imagepipeline.request.ImageRequest;
import fr.free.nrw.commons.BuildConfig; import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CameraPosition; import fr.free.nrw.commons.CameraPosition;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.LocationPicker.LocationPicker; import fr.free.nrw.commons.LocationPicker.LocationPicker;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.MediaDataExtractor; import fr.free.nrw.commons.MediaDataExtractor;
@ -57,6 +58,8 @@ import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.actions.ThanksClient; import fr.free.nrw.commons.actions.ThanksClient;
import fr.free.nrw.commons.auth.AccountUtil; import fr.free.nrw.commons.auth.AccountUtil;
import fr.free.nrw.commons.auth.SessionManager; import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.auth.csrf.CsrfTokenClient;
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException;
import fr.free.nrw.commons.category.CategoryClient; import fr.free.nrw.commons.category.CategoryClient;
import fr.free.nrw.commons.category.CategoryDetailsActivity; import fr.free.nrw.commons.category.CategoryDetailsActivity;
import fr.free.nrw.commons.category.CategoryEditHelper; import fr.free.nrw.commons.category.CategoryEditHelper;
@ -780,9 +783,23 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
firstRevision.getRevisionId())) firstRevision.getRevisionId()))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe((result) -> { .subscribe(result -> {
displayThanksToast(context, result); displayThanksToast(getContext(), result);
}, Timber::e); }, throwable -> {
if (throwable instanceof InvalidLoginTokenException) {
final String username = sessionManager.getUserName();
final CommonsApplication.BaseLogoutListener logoutListener = new CommonsApplication.BaseLogoutListener(
getActivity(),
requireActivity().getString(R.string.invalid_login_message),
username
);
CommonsApplication.getInstance().clearApplicationData(
requireActivity(), logoutListener);
} else {
Timber.e(throwable);
}
});
} }
/** /**
@ -1056,13 +1073,28 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
if (requestCode == REQUEST_CODE_EDIT_DESCRIPTION && resultCode == RESULT_OK) { if (requestCode == REQUEST_CODE_EDIT_DESCRIPTION && resultCode == RESULT_OK) {
final String updatedWikiText = data.getStringExtra(UPDATED_WIKITEXT); final String updatedWikiText = data.getStringExtra(UPDATED_WIKITEXT);
compositeDisposable.add(descriptionEditHelper.addDescription(getContext(), media,
updatedWikiText) try {
.subscribeOn(Schedulers.io()) compositeDisposable.add(descriptionEditHelper.addDescription(getContext(), media,
.observeOn(AndroidSchedulers.mainThread()) updatedWikiText)
.subscribe(s -> { .subscribeOn(Schedulers.io())
Timber.d("Descriptions are added."); .observeOn(AndroidSchedulers.mainThread())
})); .subscribe(s -> {
Timber.d("Descriptions are added.");
}));
} catch (Exception e) {
if (e.getLocalizedMessage().equals(CsrfTokenClient.ANONYMOUS_TOKEN_MESSAGE)) {
final String username = sessionManager.getUserName();
final CommonsApplication.BaseLogoutListener logoutListener = new CommonsApplication.BaseLogoutListener(
getActivity(),
requireActivity().getString(R.string.invalid_login_message),
username
);
CommonsApplication.getInstance().clearApplicationData(
requireActivity(), logoutListener);
}
}
final ArrayList<UploadMediaDetail> uploadMediaDetails final ArrayList<UploadMediaDetail> uploadMediaDetails
= data.getParcelableArrayListExtra(LIST_OF_DESCRIPTION_AND_CAPTION); = data.getParcelableArrayListExtra(LIST_OF_DESCRIPTION_AND_CAPTION);
@ -1070,14 +1102,29 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
LinkedHashMap<String, String> updatedCaptions = new LinkedHashMap<>(); LinkedHashMap<String, String> updatedCaptions = new LinkedHashMap<>();
for (UploadMediaDetail mediaDetail: for (UploadMediaDetail mediaDetail:
uploadMediaDetails) { uploadMediaDetails) {
compositeDisposable.add(descriptionEditHelper.addCaption(getContext(), media, try {
mediaDetail.getLanguageCode(), mediaDetail.getCaptionText()) compositeDisposable.add(descriptionEditHelper.addCaption(getContext(), media,
.subscribeOn(Schedulers.io()) mediaDetail.getLanguageCode(), mediaDetail.getCaptionText())
.observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io())
.subscribe(s -> { .observeOn(AndroidSchedulers.mainThread())
updateCaptions(mediaDetail, updatedCaptions); .subscribe(s -> {
Timber.d("Caption is added."); updateCaptions(mediaDetail, updatedCaptions);
})); Timber.d("Caption is added.");
}));
} catch (Exception e) {
if (e.getLocalizedMessage().equals(CsrfTokenClient.ANONYMOUS_TOKEN_MESSAGE)) {
final String username = sessionManager.getUserName();
final CommonsApplication.BaseLogoutListener logoutListener = new CommonsApplication.BaseLogoutListener(
getActivity(),
requireActivity().getString(R.string.invalid_login_message),
username
);
CommonsApplication.getInstance().clearApplicationData(
requireActivity(), logoutListener);
}
}
} }
binding.progressBarEdit.setVisibility(GONE); binding.progressBarEdit.setVisibility(GONE);
binding.descriptionEdit.setVisibility(VISIBLE); binding.descriptionEdit.setVisibility(VISIBLE);

View file

@ -12,9 +12,12 @@ import android.view.View;
import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.databinding.ActivityNotificationBinding; import fr.free.nrw.commons.databinding.ActivityNotificationBinding;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException;
import fr.free.nrw.commons.notification.models.Notification; import fr.free.nrw.commons.notification.models.Notification;
import fr.free.nrw.commons.theme.BaseActivity; import fr.free.nrw.commons.theme.BaseActivity;
import fr.free.nrw.commons.utils.NetworkUtils; import fr.free.nrw.commons.utils.NetworkUtils;
@ -41,6 +44,9 @@ public class NotificationActivity extends BaseActivity {
@Inject @Inject
NotificationController controller; NotificationController controller;
@Inject
SessionManager sessionManager;
private static final String TAG_NOTIFICATION_WORKER_FRAGMENT = "NotificationWorkerFragment"; private static final String TAG_NOTIFICATION_WORKER_FRAGMENT = "NotificationWorkerFragment";
private NotificationWorkerFragment mNotificationWorkerFragment; private NotificationWorkerFragment mNotificationWorkerFragment;
private NotificatinAdapter adapter; private NotificatinAdapter adapter;
@ -107,10 +113,23 @@ public class NotificationActivity extends BaseActivity {
ViewUtil.showLongToast(this,getString(R.string.some_error)); ViewUtil.showLongToast(this,getString(R.string.some_error));
} }
}, throwable -> { }, throwable -> {
if (throwable instanceof InvalidLoginTokenException) {
final String username = sessionManager.getUserName();
final CommonsApplication.BaseLogoutListener logoutListener = new CommonsApplication.BaseLogoutListener(
this,
getString(R.string.invalid_login_message),
username
);
Timber.e(throwable, "Error occurred while loading notifications"); CommonsApplication.getInstance().clearApplicationData(
throwable.printStackTrace(); this, logoutListener);
} else {
Timber.e(throwable, "Error occurred while loading notifications");
throwable.printStackTrace();
ViewUtil.showShortSnackbar(binding.container, R.string.error_notifications); ViewUtil.showShortSnackbar(binding.container, R.string.error_notifications);
binding.progressBar.setVisibility(View.GONE);
ViewUtil.showShortSnackbar(binding.container, R.string.error_notifications);
}
binding.progressBar.setVisibility(View.GONE); binding.progressBar.setVisibility(View.GONE);
}); });
compositeDisposable.add(disposable); compositeDisposable.add(disposable);
@ -170,7 +189,7 @@ public class NotificationActivity extends BaseActivity {
} }
binding.progressBar.setVisibility(View.GONE); binding.progressBar.setVisibility(View.GONE);
}, throwable -> { }, throwable -> {
Timber.e(throwable, "Error occurred while loading notifications"); Timber.e(throwable, "Error occurred while loading notifications ");
ViewUtil.showShortSnackbar(binding.container, R.string.error_notifications); ViewUtil.showShortSnackbar(binding.container, R.string.error_notifications);
binding.progressBar.setVisibility(View.GONE); binding.progressBar.setVisibility(View.GONE);
})); }));

View file

@ -6,6 +6,7 @@ import fr.free.nrw.commons.notification.models.NotificationType
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.Single
import fr.free.nrw.commons.auth.csrf.CsrfTokenClient import fr.free.nrw.commons.auth.csrf.CsrfTokenClient
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException
import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse
import fr.free.nrw.commons.utils.DateUtil import fr.free.nrw.commons.utils.DateUtil
import javax.inject.Inject import javax.inject.Inject
@ -39,7 +40,11 @@ class NotificationClient @Inject constructor(
unreadList = "" unreadList = ""
).map(MwQueryResponse::success) ).map(MwQueryResponse::success)
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
Observable.just(false) if (throwable is InvalidLoginTokenException) {
Observable.error(throwable)
} else {
Observable.just(false)
}
} }
} }

View file

@ -9,6 +9,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException;
import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage; import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage;
import java.util.ArrayList; import java.util.ArrayList;
@ -39,6 +41,9 @@ public class ReviewController {
protected static ArrayList<String> categories; protected static ArrayList<String> categories;
@Inject @Inject
ThanksClient thanksClient; ThanksClient thanksClient;
@Inject
SessionManager sessionManager;
private final DeleteHelper deleteHelper; private final DeleteHelper deleteHelper;
@Nullable @Nullable
MwQueryPage.Revision firstRevision; // TODO: maybe we can expand this class to include fileName MwQueryPage.Revision firstRevision; // TODO: maybe we can expand this class to include fileName
@ -155,9 +160,23 @@ public class ReviewController {
Observable.defer((Callable<ObservableSource<Boolean>>) () -> thanksClient.thank(firstRevision.getRevisionId())) Observable.defer((Callable<ObservableSource<Boolean>>) () -> thanksClient.thank(firstRevision.getRevisionId()))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe((result) -> { .subscribe(result -> {
displayThanksToast(context,result); displayThanksToast(context, result);
}, Timber::e); }, throwable -> {
if (throwable instanceof InvalidLoginTokenException) {
final String username = sessionManager.getUserName();
final CommonsApplication.BaseLogoutListener logoutListener = new CommonsApplication.BaseLogoutListener(
activity,
activity.getString(R.string.invalid_login_message),
username
);
CommonsApplication.getInstance().clearApplicationData(
activity, logoutListener);
} else {
Timber.e(throwable);
}
});
} }
@SuppressLint("StringFormatInvalid") @SuppressLint("StringFormatInvalid")
@ -192,6 +211,8 @@ public class ReviewController {
void onFailure(); void onFailure();
void onTokenException(Exception e);
void disableButtons(); void disableButtons();
void enableButtons(); void enableButtons();

View file

@ -7,15 +7,17 @@ import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException;
import fr.free.nrw.commons.databinding.FragmentReviewImageBinding; import fr.free.nrw.commons.databinding.FragmentReviewImageBinding;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.inject.Inject;
public class ReviewImageFragment extends CommonsDaggerSupportFragment { public class ReviewImageFragment extends CommonsDaggerSupportFragment {
@ -28,7 +30,8 @@ public class ReviewImageFragment extends CommonsDaggerSupportFragment {
private FragmentReviewImageBinding binding; private FragmentReviewImageBinding binding;
@Inject
SessionManager sessionManager;
// Constant variable used to store user's key name for onSaveInstanceState method // Constant variable used to store user's key name for onSaveInstanceState method
@ -37,20 +40,20 @@ public class ReviewImageFragment extends CommonsDaggerSupportFragment {
// Variable that stores the value of user // Variable that stores the value of user
private String user; private String user;
public void update(int position) { public void update(final int position) {
this.position = position; this.position = position;
} }
private String updateCategoriesQuestion() { private String updateCategoriesQuestion() {
Media media = getReviewActivity().getMedia(); final Media media = getReviewActivity().getMedia();
if (media != null && media.getCategoriesHiddenStatus() != null && isAdded()) { if (media != null && media.getCategoriesHiddenStatus() != null && isAdded()) {
// Filter category name attribute from all categories // Filter category name attribute from all categories
List<String> categories = new ArrayList<>(); final List<String> categories = new ArrayList<>();
for(String key : media.getCategoriesHiddenStatus().keySet()) { for(final String key : media.getCategoriesHiddenStatus().keySet()) {
String value = String.valueOf(key); String value = String.valueOf(key);
// Each category returned has a format like "Category:<some-category-name>" // Each category returned has a format like "Category:<some-category-name>"
// so remove the prefix "Category:" // so remove the prefix "Category:"
int index = key.indexOf("Category:"); final int index = key.indexOf("Category:");
if(index == 0) { if(index == 0) {
value = key.substring(9); value = key.substring(9);
} }
@ -59,7 +62,7 @@ public class ReviewImageFragment extends CommonsDaggerSupportFragment {
String catString = TextUtils.join(", ", categories); String catString = TextUtils.join(", ", categories);
if (catString != null && !catString.equals("") && binding.tvReviewQuestionContext != null) { if (catString != null && !catString.equals("") && binding.tvReviewQuestionContext != null) {
catString = "<b>" + catString + "</b>"; catString = "<b>" + catString + "</b>";
String stringToConvertHtml = String.format(getResources().getString(R.string.review_category_explanation), catString); final String stringToConvertHtml = String.format(getResources().getString(R.string.review_category_explanation), catString);
return Html.fromHtml(stringToConvertHtml).toString(); return Html.fromHtml(stringToConvertHtml).toString();
} }
} }
@ -67,17 +70,20 @@ public class ReviewImageFragment extends CommonsDaggerSupportFragment {
} }
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) { final Bundle savedInstanceState) {
position = getArguments().getInt("position"); position = getArguments().getInt("position");
binding = FragmentReviewImageBinding.inflate(inflater, container, false); binding = FragmentReviewImageBinding.inflate(inflater, container, false);
String question, explanation=null, yesButtonText, noButtonText; final String question;
String explanation=null;
String yesButtonText;
final String noButtonText;
binding.buttonYes.setOnClickListener(view -> onYesButtonClicked()); binding.buttonYes.setOnClickListener(view -> onYesButtonClicked());
@ -182,6 +188,22 @@ public class ReviewImageFragment extends CommonsDaggerSupportFragment {
//do nothing //do nothing
} }
@Override
public void onTokenException(final Exception e) {
if (e instanceof InvalidLoginTokenException){
final String username = sessionManager.getUserName();
final CommonsApplication.BaseLogoutListener logoutListener = new CommonsApplication.BaseLogoutListener(
getActivity(),
requireActivity().getString(R.string.invalid_login_message),
username
);
CommonsApplication.getInstance().clearApplicationData(
requireActivity(), logoutListener);
}
}
/** /**
* This function is called when an image is being loaded * This function is called when an image is being loaded
* to disable the review buttons * to disable the review buttons

View file

@ -56,6 +56,12 @@ public interface CategoriesContract {
* Refreshes the categories * Refreshes the categories
*/ */
void refreshCategories(); void refreshCategories();
/**
* Navigate the user to Login Activity
*/
void navigateToLoginScreen();
} }
interface UserActionListener extends BasePresenter<View> { interface UserActionListener extends BasePresenter<View> {

View file

@ -5,6 +5,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import fr.free.nrw.commons.Media import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException
import fr.free.nrw.commons.category.CategoryEditHelper import fr.free.nrw.commons.category.CategoryEditHelper
import fr.free.nrw.commons.category.CategoryItem import fr.free.nrw.commons.category.CategoryItem
import fr.free.nrw.commons.di.CommonsApplicationModule import fr.free.nrw.commons.di.CommonsApplicationModule
@ -211,25 +212,31 @@ class CategoriesPresenter @Inject constructor(
if (selectedCategories.isNotEmpty()) { if (selectedCategories.isNotEmpty()) {
view.showProgressDialog() view.showProgressDialog()
compositeDisposable.add(
categoryEditHelper.makeCategoryEdit(view.fragmentContext, media, try {
selectedCategories, wikiText) compositeDisposable.add(
.subscribeOn(Schedulers.io()) categoryEditHelper.makeCategoryEdit(
.observeOn(AndroidSchedulers.mainThread()) view.fragmentContext, media,
.subscribe({ selectedCategories, wikiText
Timber.d("Categories are added.") )
media.addedCategories = selectedCategories .subscribeOn(Schedulers.io())
repository.cleanup() .observeOn(AndroidSchedulers.mainThread())
view.dismissProgressDialog() .subscribe({
view.refreshCategories() Timber.d("Categories are added.")
view.goBackToPreviousScreen() media.addedCategories = selectedCategories
}) repository.cleanup()
{ view.dismissProgressDialog()
Timber.e( view.refreshCategories()
"Failed to update categories" view.goBackToPreviousScreen()
) }, {
} Timber.e(
) "Failed to update categories"
)
})
)
} catch (e: InvalidLoginTokenException) {
view.navigateToLoginScreen();
}
} }
} else { } else {

View file

@ -19,8 +19,10 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import com.jakewharton.rxbinding2.view.RxView; import com.jakewharton.rxbinding2.view.RxView;
import com.jakewharton.rxbinding2.widget.RxTextView; import com.jakewharton.rxbinding2.widget.RxTextView;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.category.CategoryItem; import fr.free.nrw.commons.category.CategoryItem;
import fr.free.nrw.commons.contributions.ContributionsFragment; import fr.free.nrw.commons.contributions.ContributionsFragment;
import fr.free.nrw.commons.databinding.UploadCategoriesFragmentBinding; import fr.free.nrw.commons.databinding.UploadCategoriesFragmentBinding;
@ -41,6 +43,8 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate
@Inject @Inject
CategoriesContract.UserActionListener presenter; CategoriesContract.UserActionListener presenter;
@Inject
SessionManager sessionManager;
private UploadCategoryAdapter adapter; private UploadCategoryAdapter adapter;
private Disposable subscribe; private Disposable subscribe;
/** /**
@ -295,6 +299,22 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate
mediaDetailFragment.updateCategories(); mediaDetailFragment.updateCategories();
} }
/**
*
*/
@Override
public void navigateToLoginScreen() {
final String username = sessionManager.getUserName();
final CommonsApplication.BaseLogoutListener logoutListener = new CommonsApplication.BaseLogoutListener(
getActivity(),
requireActivity().getString(R.string.invalid_login_message),
username
);
CommonsApplication.getInstance().clearApplicationData(
requireActivity(), logoutListener);
}
public void onNextButtonClicked() { public void onNextButtonClicked() {
if (media != null) { if (media != null) {
presenter.updateCategories(media, wikiText); presenter.updateCategories(media, wikiText);

View file

@ -73,6 +73,11 @@ public interface DepictsContract {
* Update the depictions * Update the depictions
*/ */
void updateDepicts(); void updateDepicts();
/**
* Navigate the user to Login Activity
*/
void navigateToLoginScreen();
} }
interface UserActionListener extends BasePresenter<View> { interface UserActionListener extends BasePresenter<View> {

View file

@ -17,8 +17,10 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import com.jakewharton.rxbinding2.view.RxView; import com.jakewharton.rxbinding2.view.RxView;
import com.jakewharton.rxbinding2.widget.RxTextView; import com.jakewharton.rxbinding2.widget.RxTextView;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.ContributionsFragment; import fr.free.nrw.commons.contributions.ContributionsFragment;
import fr.free.nrw.commons.databinding.UploadDepictsFragmentBinding; import fr.free.nrw.commons.databinding.UploadDepictsFragmentBinding;
import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.kvstore.JsonKvStore;
@ -63,6 +65,9 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
private UploadDepictsFragmentBinding binding; private UploadDepictsFragmentBinding binding;
@Inject
SessionManager sessionManager;
@Nullable @Nullable
@Override @Override
public android.view.View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, public android.view.View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@ -311,6 +316,22 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
mediaDetailFragment.onResume(); mediaDetailFragment.onResume();
} }
/**
* Navigates to the login Activity
*/
@Override
public void navigateToLoginScreen() {
final String username = sessionManager.getUserName();
final CommonsApplication.BaseLogoutListener logoutListener = new CommonsApplication.BaseLogoutListener(
getActivity(),
requireActivity().getString(R.string.invalid_login_message),
username
);
CommonsApplication.getInstance().clearApplicationData(
requireActivity(), logoutListener);
}
/** /**
* Determines the calling fragment by media nullability and act accordingly * Determines the calling fragment by media nullability and act accordingly
*/ */

View file

@ -5,6 +5,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsController import fr.free.nrw.commons.bookmarks.items.BookmarkItemsController
import fr.free.nrw.commons.Media import fr.free.nrw.commons.Media
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException
import fr.free.nrw.commons.di.CommonsApplicationModule import fr.free.nrw.commons.di.CommonsApplicationModule
import fr.free.nrw.commons.repository.UploadRepository import fr.free.nrw.commons.repository.UploadRepository
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
@ -218,13 +219,18 @@ class DepictsPresenter @Inject constructor(
view.dismissProgressDialog() view.dismissProgressDialog()
view.updateDepicts() view.updateDepicts()
view.goBackToPreviousScreen() view.goBackToPreviousScreen()
}, { error ->
if (error is InvalidLoginTokenException) {
view.navigateToLoginScreen();
} else {
Timber.e(
"Failed to update depictions"
)
}
}) })
{
Timber.e(
"Failed to update depictions"
)
}
) )
} }
} else { } else {
repository.cleanup() repository.cleanup()

View file

@ -59,11 +59,6 @@ class WikiBaseClient @Inject constructor(
} }
private fun csrfToken(): Observable<String> = Observable.fromCallable { private fun csrfToken(): Observable<String> = Observable.fromCallable {
try { csrfTokenClient.getTokenBlocking()
csrfTokenClient.getTokenBlocking()
} catch (throwable: Throwable) {
Timber.e(throwable)
""
}
} }
} }

View file

@ -8,6 +8,7 @@ import android.content.Context;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.gson.Gson; import com.google.gson.Gson;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException;
import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.upload.UploadResult; import fr.free.nrw.commons.upload.UploadResult;
@ -123,8 +124,13 @@ public class WikidataEditService {
} }
}) })
.doOnError(throwable -> { .doOnError(throwable -> {
Timber.e(throwable, "Error occurred while setting DEPICTS property"); if (throwable instanceof InvalidLoginTokenException) {
ViewUtil.showLongToast(context, throwable.toString()); Observable.error(throwable);
} else {
Timber.e(throwable, "Error occurred while setting DEPICTS property");
ViewUtil.showLongToast(context, throwable.toString());
}
}) })
.subscribeOn(Schedulers.io()); .subscribeOn(Schedulers.io());
} }