package org.wikipedia.settings;
import android.support.annotation.IntRange;
import android.support.annotation.Nullable;
import org.wikipedia.WikipediaApp;
import org.wikipedia.dataclient.WikiSite;
import org.wikipedia.dataclient.okhttp.HttpStatusException;
import org.wikipedia.dataclient.retrofit.RetrofitException;
import org.wikipedia.util.ReleaseUtil;
import java.util.Random;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
/**
* This class encapsulates logic to turn on or off usage of our RESTBase service for a certain
* percentage of beta (=non-production) app installs. The percentage is remote controlled,
* so we can turn it off if necessary and can better control the roll-out in the unlikely case
* that the new content service cannot handle the load from the app.
* It also has an automatic fallback to using the MW API after the first significant error.
* So, 404s and network errors are ignored.
*/
public final class RbSwitch {
public static final int FAILED = -1;
private static final int HUNDRED_PERCENT = 100;
private static final int SUCCESS_THRESHOLD = 5; // page loads
private static final String ENABLE_RESTBASE_PERCENT_CONFIG_KEY = ReleaseUtil.isProdRelease()
? "restbaseProdPercent" : "restbaseBetaPercent";
public static final RbSwitch INSTANCE = new RbSwitch();
/**
* Returns true if RB is enabled for a particular WikiSite (or PageTitle if you will).
* This method has a few extra checks over the overloaded #isRestBaseEnabled():
* It disables RB also when image download is disabled since the noimages parameter is
* not functional yet. T119161
* It also disables RB usage if the wiki is zhwiki since RB endpoints have a harder time
* dealing with caching of language variants. T118905
* @param wiki the WikiSite of the PageTitle to use for the check
* @return true is RB is enabled for a particular WikiSite
*/
public boolean isRestBaseEnabled(WikiSite wiki) {
return isRestBaseEnabled()
&& Prefs.isImageDownloadEnabled()
&& !wiki.languageCode().startsWith("zh");
}
public boolean isRestBaseEnabled() {
return Prefs.useRestBase();
}
public void update() {
if (!Prefs.useRestBaseSetManually()) {
Prefs.setUseRestBase(shouldUseRestBase());
}
}
private static boolean shouldUseRestBase() {
return isSlatedForRestBase() && hasNotRecentlyFailed();
}
private static boolean isSlatedForRestBase() {
int ticket = Prefs.getRbTicket(0);
if (ticket == 0) {
ticket = new Random().nextInt(HUNDRED_PERCENT) + 1; // [1, 100]
Prefs.setRbTicket(ticket);
}
return isAdmitted(ticket, ENABLE_RESTBASE_PERCENT_CONFIG_KEY);
}
private static boolean isAdmitted(@IntRange(from = 1, to = 100) int ticket, String configKey) {
@IntRange(from = 0, to = 100) int admittedPct = WikipediaApp.getInstance()
.getRemoteConfig().getConfig().optInt(configKey, 0); // 0 = disable
return ticket <= admittedPct;
}
private static boolean hasNotRecentlyFailed() {
return Prefs.getRequestSuccessCounter(0) >= 0;
}
/**
* For automatically bouncing back from MW API to RB API after SUCCESS_THRESHOLD number of
* successful requests happened after #markRbFailed.
*/
public void onMwSuccess() {
if (isSlatedForRestBase()) {
int successes = Prefs.getRequestSuccessCounter(0);
successes++;
Prefs.setRequestSuccessCounter(successes);
resetFailed();
if (successes >= SUCCESS_THRESHOLD && !Prefs.useRestBaseSetManually()) {
Prefs.setUseRestBase(true);
}
}
}
/**
* Call this method when a RESTBase call fails.
*/
public void onRbRequestFailed(@Nullable Throwable error) {
if (isSignificantFailure(error)) {
markRbFailed();
if (!Prefs.useRestBaseSetManually()) {
Prefs.setUseRestBase(false);
}
}
}
/**
* Determines if an error is significant enough to warrant a fallback to MwApi.
* We don't want to fallback just because of a user error (404)
* or a network issue on the client side (RetrofitError.Kind.NETWORK).
*/
private static boolean isSignificantFailure(@Nullable Throwable throwable) {
if (throwable instanceof RetrofitException) {
RetrofitException error = (RetrofitException) throwable;
if (error.getKind() == RetrofitException.Kind.HTTP) {
return error.getCode() != null && error.getCode() != HTTP_NOT_FOUND;
}
return error.getKind() != RetrofitException.Kind.NETWORK;
}
if (throwable instanceof HttpStatusException) {
HttpStatusException e = (HttpStatusException) throwable;
return e.code() != HTTP_NOT_FOUND;
}
return false;
}
private static void markRbFailed() {
Prefs.setRequestSuccessCounter(FAILED);
}
private static void resetFailed() {
Prefs.setRequestSuccessCounter(0);
}
private RbSwitch() {
update();
}
}