mirror of
https://github.com/revanced/revanced-integrations.git
synced 2025-01-05 17:45:49 +01:00
feat(youtube/return-youtube-dislike): debug connection statistics, toast on error, high priority background threads (#236)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
This commit is contained in:
parent
84377b2da4
commit
693ef08c6c
@ -26,28 +26,37 @@ public class Requester {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse, and then disconnect the {@link HttpURLConnection}
|
||||
*
|
||||
* TODO: rename this to #parseJsonAndDisconnect
|
||||
* Parse the {@link HttpURLConnection}, and closes the underlying InputStream.
|
||||
*/
|
||||
public static String parseJson(HttpURLConnection connection) throws IOException {
|
||||
String result = parseJson(connection.getInputStream(), false);
|
||||
return parseInputStreamAndClose(connection.getInputStream(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the {@link HttpURLConnection}, close the underlying InputStream, and disconnect.
|
||||
*
|
||||
* <b>Should only be used if other requests to the server are unlikely in the near future</b>
|
||||
*
|
||||
* @see #parseJson(HttpURLConnection)
|
||||
*/
|
||||
public static String parseJsonAndDisconnect(HttpURLConnection connection) throws IOException {
|
||||
String result = parseJson(connection);
|
||||
connection.disconnect();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse, and then close the {@link InputStream}
|
||||
* Parse the {@link HttpURLConnection}, and closes the underlying InputStream.
|
||||
*
|
||||
* TODO: rename this to #parseJsonAndCloseStream
|
||||
* @param stripNewLineCharacters if newline (\n) characters should be stripped from the InputStream
|
||||
*/
|
||||
public static String parseJson(InputStream inputStream, boolean isError) throws IOException {
|
||||
public static String parseInputStreamAndClose(InputStream inputStream, boolean stripNewLineCharacters) throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
|
||||
StringBuilder jsonBuilder = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
jsonBuilder.append(line);
|
||||
if (isError)
|
||||
if (!stripNewLineCharacters)
|
||||
jsonBuilder.append("\n");
|
||||
}
|
||||
return jsonBuilder.toString();
|
||||
@ -55,34 +64,63 @@ public class Requester {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse, and then do NOT disconnect the {@link HttpURLConnection}
|
||||
* Parse the {@link HttpURLConnection}, and closes the underlying InputStream.
|
||||
*/
|
||||
public static String parseErrorJson(HttpURLConnection connection) throws IOException {
|
||||
// TODO: make this also disconnect, and rename method to #parseErrorJsonAndDisconnect
|
||||
return parseJson(connection.getErrorStream(), true);
|
||||
return parseInputStreamAndClose(connection.getErrorStream(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse, and then disconnect the {@link HttpURLConnection}
|
||||
* Parse the {@link HttpURLConnection}, close the underlying InputStream, and disconnect.
|
||||
*
|
||||
* TODO: rename this to #getJSONObjectAndDisconnect
|
||||
*/
|
||||
public static JSONObject getJSONObject(HttpURLConnection connection) throws JSONException, IOException {
|
||||
return new JSONObject(parseJsonAndDisconnect(connection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse, and then disconnect the {@link HttpURLConnection}
|
||||
* <b>Should only be used if other requests to the server are unlikely in the near future</b>
|
||||
*
|
||||
* TODO: rename this to #getJSONArrayAndDisconnect
|
||||
* @see #parseErrorJson(HttpURLConnection)
|
||||
*/
|
||||
public static JSONArray getJSONArray(HttpURLConnection connection) throws JSONException, IOException {
|
||||
return new JSONArray(parseJsonAndDisconnect(connection));
|
||||
}
|
||||
|
||||
private static String parseJsonAndDisconnect(HttpURLConnection connection) throws IOException {
|
||||
String json = parseJson(connection);
|
||||
public static String parseErrorJsonAndDisconnect(HttpURLConnection connection) throws IOException {
|
||||
String result = parseErrorJson(connection);
|
||||
connection.disconnect();
|
||||
return json;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the {@link HttpURLConnection}, and closes the underlying InputStream.
|
||||
*/
|
||||
public static JSONObject parseJSONObject(HttpURLConnection connection) throws JSONException, IOException {
|
||||
return new JSONObject(parseJson(connection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the {@link HttpURLConnection}, close the underlying InputStream, and disconnect.
|
||||
*
|
||||
* <b>Should only be used if other requests to the server are unlikely in the near future</b>
|
||||
*
|
||||
* @see #parseJSONObject(HttpURLConnection)
|
||||
*/
|
||||
public static JSONObject parseJSONObjectAndDisconnect(HttpURLConnection connection) throws JSONException, IOException {
|
||||
JSONObject object = parseJSONObject(connection);
|
||||
connection.disconnect();
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the {@link HttpURLConnection}, and closes the underlying InputStream.
|
||||
*/
|
||||
public static JSONArray parseJSONArray(HttpURLConnection connection) throws JSONException, IOException {
|
||||
return new JSONArray(parseJson(connection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the {@link HttpURLConnection}, close the underlying InputStream, and disconnect.
|
||||
*
|
||||
* <b>Should only be used if other requests to the server are unlikely in the near future</b>
|
||||
*
|
||||
* @see #parseJSONArray(HttpURLConnection)
|
||||
*/
|
||||
public static JSONArray parseJSONArrayAndDisconnect(HttpURLConnection connection) throws JSONException, IOException {
|
||||
JSONArray array = parseJSONArray(connection);
|
||||
connection.disconnect();
|
||||
return array;
|
||||
}
|
||||
|
||||
}
|
@ -40,7 +40,7 @@ public class ReturnYouTubeDislike {
|
||||
*/
|
||||
private static final ExecutorService voteSerialExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
// Must be volatile, since non-main threads read this field.
|
||||
// Must be volatile, since this is read/write from different threads
|
||||
private static volatile boolean isEnabled = SettingsEnum.RYD_ENABLED.getBoolean();
|
||||
|
||||
/**
|
||||
@ -138,11 +138,18 @@ public class ReturnYouTubeDislike {
|
||||
// Have to block the current thread until fetching is done
|
||||
// There's no known way to edit the text after creation yet
|
||||
RYDVoteData votingData;
|
||||
long fetchStartTime = 0;
|
||||
try {
|
||||
votingData = getVoteFetchFuture().get(MILLISECONDS_TO_BLOCK_UI_WHILE_WAITING_FOR_DISLIKE_FETCH_TO_COMPLETE, TimeUnit.MILLISECONDS);
|
||||
Future<RYDVoteData> fetchFuture = getVoteFetchFuture();
|
||||
if (SettingsEnum.DEBUG.getBoolean() && !fetchFuture.isDone()) {
|
||||
fetchStartTime = System.currentTimeMillis();
|
||||
}
|
||||
votingData = fetchFuture.get(MILLISECONDS_TO_BLOCK_UI_WHILE_WAITING_FOR_DISLIKE_FETCH_TO_COMPLETE, TimeUnit.MILLISECONDS);
|
||||
} catch (TimeoutException e) {
|
||||
LogHelper.printDebug(() -> "UI timed out waiting for dislike fetch to complete");
|
||||
return;
|
||||
} finally {
|
||||
recordTimeUISpentWaitingForNetworkCall(fetchStartTime);
|
||||
}
|
||||
if (votingData == null) {
|
||||
LogHelper.printDebug(() -> "Cannot add dislike count to UI (RYD data not available)");
|
||||
@ -293,4 +300,30 @@ public class ReturnYouTubeDislike {
|
||||
// never will be reached, as the oldest supported YouTube app requires Android N or greater
|
||||
return (int) (100 * dislikePercentage) + "%";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Number of times the UI was forced to wait on a network fetch to complete
|
||||
*/
|
||||
private static volatile int numberOfTimesUIWaitedOnNetworkCalls;
|
||||
|
||||
/**
|
||||
* Total time the UI waited, of all times it was forced to wait.
|
||||
*/
|
||||
private static volatile long totalTimeUIWaitedOnNetworkCalls;
|
||||
|
||||
private static void recordTimeUISpentWaitingForNetworkCall(long timeUIWaitStarted) {
|
||||
if (timeUIWaitStarted == 0 || !SettingsEnum.DEBUG.getBoolean()) {
|
||||
return;
|
||||
}
|
||||
final long timeUIWaitingTotal = System.currentTimeMillis() - timeUIWaitStarted;
|
||||
LogHelper.printDebug(() -> "UI thread waited for: " + timeUIWaitingTotal + "ms for vote fetch to complete");
|
||||
|
||||
totalTimeUIWaitedOnNetworkCalls += timeUIWaitingTotal;
|
||||
numberOfTimesUIWaitedOnNetworkCalls++;
|
||||
final long averageTimeForcedToWait = totalTimeUIWaitedOnNetworkCalls / numberOfTimesUIWaitedOnNetworkCalls;
|
||||
LogHelper.printDebug(() -> "UI thread forced to wait: " + numberOfTimesUIWaitedOnNetworkCalls + " times, "
|
||||
+ "total wait time: " + totalTimeUIWaitedOnNetworkCalls + "ms, "
|
||||
+ "average wait time: " + averageTimeForcedToWait + "ms") ;
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,16 @@
|
||||
package app.revanced.integrations.returnyoutubedislike.requests;
|
||||
|
||||
import static app.revanced.integrations.returnyoutubedislike.requests.ReturnYouTubeDislikeRoutes.getRYDConnectionFromRoute;
|
||||
import static app.revanced.integrations.sponsorblock.StringRef.str;
|
||||
|
||||
import android.util.Base64;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.ProtocolException;
|
||||
@ -18,18 +21,21 @@ import java.security.SecureRandom;
|
||||
import java.util.Objects;
|
||||
|
||||
import app.revanced.integrations.requests.Requester;
|
||||
import app.revanced.integrations.requests.Route;
|
||||
import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike;
|
||||
import app.revanced.integrations.utils.LogHelper;
|
||||
import app.revanced.integrations.utils.ReVancedUtils;
|
||||
|
||||
public class ReturnYouTubeDislikeApi {
|
||||
private static final String RYD_API_URL = "https://returnyoutubedislikeapi.com/";
|
||||
/**
|
||||
* {@link #fetchVotes(String)} TCP connection timeout
|
||||
*/
|
||||
private static final int API_GET_VOTES_TCP_TIMEOUT_MILLISECONDS = 2000;
|
||||
|
||||
/**
|
||||
* Default connection and response timeout for {@link #fetchVotes(String)}
|
||||
* {@link #fetchVotes(String)} HTTP read timeout
|
||||
* To locally debug and force timeouts, change this to a very small number (ie: 100)
|
||||
*/
|
||||
private static final int API_GET_DISLIKE_DEFAULT_TIMEOUT_MILLISECONDS = 5000;
|
||||
private static final int API_GET_VOTES_HTTP_TIMEOUT_MILLISECONDS = 4000;
|
||||
|
||||
/**
|
||||
* Default connection and response timeout for voting and registration.
|
||||
@ -37,7 +43,7 @@ public class ReturnYouTubeDislikeApi {
|
||||
* Voting and user registration runs in the background and has has no urgency
|
||||
* so this can be a larger value.
|
||||
*/
|
||||
private static final int API_REGISTER_VOTE_DEFAULT_TIMEOUT_MILLISECONDS = 90000;
|
||||
private static final int API_REGISTER_VOTE_TIMEOUT_MILLISECONDS = 90000;
|
||||
|
||||
/**
|
||||
* Response code of a successful API call
|
||||
@ -59,19 +65,76 @@ public class ReturnYouTubeDislikeApi {
|
||||
* Last time a {@link #RATE_LIMIT_HTTP_STATUS_CODE} was reached.
|
||||
* zero if has not been reached.
|
||||
*/
|
||||
private static volatile long lastTimeLimitWasHit; // must be volatile, since different threads read/write to this
|
||||
private static volatile long lastTimeRateLimitWasHit; // must be volatile, since different threads read/write to this
|
||||
|
||||
/**
|
||||
* Number of times {@link #RATE_LIMIT_HTTP_STATUS_CODE} was requested by RYD api.
|
||||
* Does not include network calls attempted while rate limit is in effect
|
||||
*/
|
||||
private static volatile int numberOfRateLimitRequestsEncountered;
|
||||
|
||||
/**
|
||||
* Number of network calls made in {@link #fetchVotes(String)}
|
||||
*/
|
||||
private static volatile int fetchCallCount;
|
||||
|
||||
/**
|
||||
* Number of times {@link #fetchVotes(String)} failed due to timeout or any other error.
|
||||
* This does not include when rate limit requests are encountered.
|
||||
*/
|
||||
private static volatile int fetchCallNumberOfFailures;
|
||||
|
||||
/**
|
||||
* Total time spent waiting for {@link #fetchVotes(String)} network call to complete.
|
||||
* Value does does not persist on app shut down.
|
||||
*/
|
||||
private static volatile long fetchCallResponseTimeTotal;
|
||||
|
||||
/**
|
||||
* Round trip network time for the most recent call to {@link #fetchVotes(String)}
|
||||
*/
|
||||
private static volatile long fetchCallResponseTimeLast;
|
||||
private static volatile long fetchCallResponseTimeMin;
|
||||
private static volatile long fetchCallResponseTimeMax;
|
||||
|
||||
public static final int FETCH_CALL_RESPONSE_TIME_VALUE_RATE_LIMIT = -2;
|
||||
|
||||
/**
|
||||
* If rate limit was hit, this returns {@link #FETCH_CALL_RESPONSE_TIME_VALUE_RATE_LIMIT}
|
||||
*/
|
||||
public static long getFetchCallResponseTimeLast() {
|
||||
return fetchCallResponseTimeLast;
|
||||
}
|
||||
public static long getFetchCallResponseTimeMin() {
|
||||
return fetchCallResponseTimeMin;
|
||||
}
|
||||
public static long getFetchCallResponseTimeMax() {
|
||||
return fetchCallResponseTimeMax;
|
||||
}
|
||||
public static long getFetchCallResponseTimeAverage() {
|
||||
return fetchCallCount == 0 ? 0 : (fetchCallResponseTimeTotal / fetchCallCount);
|
||||
}
|
||||
public static int getFetchCallCount() {
|
||||
return fetchCallCount;
|
||||
}
|
||||
public static int getFetchCallNumberOfFailures() {
|
||||
return fetchCallNumberOfFailures;
|
||||
}
|
||||
public static int getNumberOfRateLimitRequestsEncountered() {
|
||||
return numberOfRateLimitRequestsEncountered;
|
||||
}
|
||||
|
||||
private ReturnYouTubeDislikeApi() {
|
||||
} // utility class
|
||||
|
||||
/**
|
||||
* Only for local debugging to simulate a slow api call.
|
||||
* Does this by doing meaningless calculations.
|
||||
* Only to simulate a slow api call, for debugging the app UI with slow url calls.
|
||||
* Simulates a slow response by doing meaningless calculations.
|
||||
*
|
||||
* @param maximumTimeToWait maximum time to wait
|
||||
*/
|
||||
private static long randomlyWaitIfLocallyDebugging(long maximumTimeToWait) {
|
||||
final boolean DEBUG_RANDOMLY_DELAY_NETWORK_CALLS = false;
|
||||
final boolean DEBUG_RANDOMLY_DELAY_NETWORK_CALLS = false; // set true to debug UI
|
||||
if (DEBUG_RANDOMLY_DELAY_NETWORK_CALLS) {
|
||||
final long amountOfTimeToWaste = (long) (Math.random() * maximumTimeToWait);
|
||||
final long timeCalculationStarted = System.currentTimeMillis();
|
||||
@ -93,10 +156,10 @@ public class ReturnYouTubeDislikeApi {
|
||||
* @return True, if api rate limit is in effect.
|
||||
*/
|
||||
private static boolean checkIfRateLimitInEffect(String apiEndPointName) {
|
||||
if (lastTimeLimitWasHit == 0) {
|
||||
if (lastTimeRateLimitWasHit == 0) {
|
||||
return false;
|
||||
}
|
||||
final long numberOfSecondsSinceLastRateLimit = (System.currentTimeMillis() - lastTimeLimitWasHit) / 1000;
|
||||
final long numberOfSecondsSinceLastRateLimit = (System.currentTimeMillis() - lastTimeRateLimitWasHit) / 1000;
|
||||
if (numberOfSecondsSinceLastRateLimit < RATE_LIMIT_BACKOFF_SECONDS) {
|
||||
LogHelper.printDebug(() -> "Ignoring api call " + apiEndPointName + " as only "
|
||||
+ numberOfSecondsSinceLastRateLimit + " seconds has passed since last rate limit.");
|
||||
@ -106,13 +169,12 @@ public class ReturnYouTubeDislikeApi {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True, if the rate limit was reached.
|
||||
* @return True, if a client rate limit was requested
|
||||
*/
|
||||
private static boolean checkIfRateLimitWasHit(int httpResponseCode) {
|
||||
// set to true, to verify rate limit works
|
||||
final boolean DEBUG_RATE_LIMIT = false;
|
||||
final boolean DEBUG_RATE_LIMIT = false; // set to true, to verify rate limit works
|
||||
if (DEBUG_RATE_LIMIT) {
|
||||
final double RANDOM_RATE_LIMIT_PERCENTAGE = 0.1; // 10% chance of a triggering a rate limit
|
||||
final double RANDOM_RATE_LIMIT_PERCENTAGE = 0.2; // 20% chance of a triggering a rate limit
|
||||
if (Math.random() < RANDOM_RATE_LIMIT_PERCENTAGE) {
|
||||
LogHelper.printDebug(() -> "Artificially triggering rate limit for debug purposes");
|
||||
httpResponseCode = RATE_LIMIT_HTTP_STATUS_CODE;
|
||||
@ -120,7 +182,7 @@ public class ReturnYouTubeDislikeApi {
|
||||
}
|
||||
|
||||
if (httpResponseCode == RATE_LIMIT_HTTP_STATUS_CODE) {
|
||||
lastTimeLimitWasHit = System.currentTimeMillis();
|
||||
lastTimeRateLimitWasHit = System.currentTimeMillis();
|
||||
LogHelper.printDebug(() -> "API rate limit was hit. Stopping API calls for the next "
|
||||
+ RATE_LIMIT_BACKOFF_SECONDS + " seconds");
|
||||
return true;
|
||||
@ -128,6 +190,28 @@ public class ReturnYouTubeDislikeApi {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void updateStatistics(long timeNetworkCallStarted, long timeNetworkCallEnded, boolean connectionError, boolean rateLimitHit) {
|
||||
if (connectionError && rateLimitHit) {
|
||||
throw new IllegalArgumentException("both connection error and rate limit parameter were true");
|
||||
}
|
||||
final long responseTimeOfFetchCall = timeNetworkCallEnded - timeNetworkCallStarted;
|
||||
fetchCallResponseTimeTotal += responseTimeOfFetchCall;
|
||||
fetchCallResponseTimeMin = (fetchCallResponseTimeMin == 0) ? responseTimeOfFetchCall : Math.min(responseTimeOfFetchCall, fetchCallResponseTimeMin);
|
||||
fetchCallResponseTimeMax = Math.max(responseTimeOfFetchCall, fetchCallResponseTimeMax);
|
||||
fetchCallCount++;
|
||||
if (connectionError) {
|
||||
fetchCallResponseTimeLast = responseTimeOfFetchCall;
|
||||
fetchCallNumberOfFailures++;
|
||||
showToast("revanced_ryd_failure_connection_timeout");
|
||||
} else if (rateLimitHit) {
|
||||
fetchCallResponseTimeLast = FETCH_CALL_RESPONSE_TIME_VALUE_RATE_LIMIT;
|
||||
numberOfRateLimitRequestsEncountered++;
|
||||
showToast("revanced_ryd_failure_client_rate_limit_requested");
|
||||
} else {
|
||||
fetchCallResponseTimeLast = responseTimeOfFetchCall;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NULL if fetch failed, or if a rate limit is in effect.
|
||||
*/
|
||||
@ -135,13 +219,15 @@ public class ReturnYouTubeDislikeApi {
|
||||
public static RYDVoteData fetchVotes(String videoId) {
|
||||
ReVancedUtils.verifyOffMainThread();
|
||||
Objects.requireNonNull(videoId);
|
||||
try {
|
||||
if (checkIfRateLimitInEffect("fetchDislikes")) {
|
||||
return null;
|
||||
}
|
||||
LogHelper.printDebug(() -> "Fetching dislikes for: " + videoId);
|
||||
|
||||
HttpURLConnection connection = getConnectionFromRoute(ReturnYouTubeDislikeRoutes.GET_DISLIKES, videoId);
|
||||
if (checkIfRateLimitInEffect("fetchVotes")) {
|
||||
return null;
|
||||
}
|
||||
LogHelper.printDebug(() -> "Fetching votes for: " + videoId);
|
||||
final long timeNetworkCallStarted = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
HttpURLConnection connection = getRYDConnectionFromRoute(ReturnYouTubeDislikeRoutes.GET_DISLIKES, videoId);
|
||||
// request headers, as per https://returnyoutubedislike.com/docs/fetching
|
||||
// the documentation says to use 'Accept:text/html', but the RYD browser plugin uses 'Accept:application/json'
|
||||
connection.setRequestProperty("Accept", "application/json");
|
||||
@ -149,33 +235,41 @@ public class ReturnYouTubeDislikeApi {
|
||||
connection.setRequestProperty("Pragma", "no-cache");
|
||||
connection.setRequestProperty("Cache-Control", "no-cache");
|
||||
connection.setUseCaches(false);
|
||||
connection.setConnectTimeout(API_GET_DISLIKE_DEFAULT_TIMEOUT_MILLISECONDS); // timeout for TCP connection to server
|
||||
connection.setReadTimeout(API_GET_DISLIKE_DEFAULT_TIMEOUT_MILLISECONDS); // timeout for server response
|
||||
connection.setConnectTimeout(API_GET_VOTES_TCP_TIMEOUT_MILLISECONDS); // timeout for TCP connection to server
|
||||
connection.setReadTimeout(API_GET_VOTES_HTTP_TIMEOUT_MILLISECONDS); // timeout for server response
|
||||
|
||||
randomlyWaitIfLocallyDebugging(2 * API_GET_DISLIKE_DEFAULT_TIMEOUT_MILLISECONDS);
|
||||
randomlyWaitIfLocallyDebugging(2*(API_GET_VOTES_TCP_TIMEOUT_MILLISECONDS + API_GET_VOTES_HTTP_TIMEOUT_MILLISECONDS));
|
||||
|
||||
final int responseCode = connection.getResponseCode();
|
||||
if (checkIfRateLimitWasHit(responseCode)) {
|
||||
connection.disconnect();
|
||||
connection.disconnect(); // rate limit hit, should disconnect
|
||||
updateStatistics(timeNetworkCallStarted, System.currentTimeMillis(),false, true);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (responseCode == SUCCESS_HTTP_STATUS_CODE) {
|
||||
JSONObject json = Requester.getJSONObject(connection); // also disconnects
|
||||
final long timeNetworkCallEnded = System.currentTimeMillis(); // record end time before parsing
|
||||
// do not disconnect, the same server connection will likely be used again soon
|
||||
JSONObject json = Requester.parseJSONObject(connection);
|
||||
try {
|
||||
RYDVoteData votingData = new RYDVoteData(json);
|
||||
updateStatistics(timeNetworkCallStarted, timeNetworkCallEnded, false, false);
|
||||
LogHelper.printDebug(() -> "Voting data fetched: " + votingData);
|
||||
return votingData;
|
||||
} catch (JSONException ex) {
|
||||
LogHelper.printException(() -> "Failed to parse video: " + videoId + " json: " + json, ex);
|
||||
return null;
|
||||
// fall thru to update statistics
|
||||
}
|
||||
} else {
|
||||
LogHelper.printDebug(() -> "Failed to fetch votes for video: " + videoId
|
||||
+ " response code was: " + responseCode);
|
||||
connection.disconnect(); // something went wrong, might as well disconnect
|
||||
}
|
||||
LogHelper.printDebug(() -> "Failed to fetch dislikes for video: " + videoId
|
||||
+ " response code was: " + responseCode);
|
||||
connection.disconnect();
|
||||
} catch (Exception ex) {
|
||||
LogHelper.printException(() -> "Failed to fetch dislikes", ex);
|
||||
} catch (Exception ex) { // connection timed out, response timeout, or some other network error
|
||||
LogHelper.printException(() -> "Failed to fetch votes", ex);
|
||||
}
|
||||
|
||||
updateStatistics(timeNetworkCallStarted, System.currentTimeMillis(), true, false);
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -192,18 +286,18 @@ public class ReturnYouTubeDislikeApi {
|
||||
String userId = randomString(36);
|
||||
LogHelper.printDebug(() -> "Trying to register new user: " + userId);
|
||||
|
||||
HttpURLConnection connection = getConnectionFromRoute(ReturnYouTubeDislikeRoutes.GET_REGISTRATION, userId);
|
||||
HttpURLConnection connection = getRYDConnectionFromRoute(ReturnYouTubeDislikeRoutes.GET_REGISTRATION, userId);
|
||||
connection.setRequestProperty("Accept", "application/json");
|
||||
connection.setConnectTimeout(API_REGISTER_VOTE_DEFAULT_TIMEOUT_MILLISECONDS);
|
||||
connection.setReadTimeout(API_REGISTER_VOTE_DEFAULT_TIMEOUT_MILLISECONDS);
|
||||
connection.setConnectTimeout(API_REGISTER_VOTE_TIMEOUT_MILLISECONDS);
|
||||
connection.setReadTimeout(API_REGISTER_VOTE_TIMEOUT_MILLISECONDS);
|
||||
|
||||
final int responseCode = connection.getResponseCode();
|
||||
if (checkIfRateLimitWasHit(responseCode)) {
|
||||
connection.disconnect();
|
||||
connection.disconnect(); // disconnect, as no more connections will be made for a little while
|
||||
return null;
|
||||
}
|
||||
if (responseCode == SUCCESS_HTTP_STATUS_CODE) {
|
||||
JSONObject json = Requester.getJSONObject(connection); // also disconnects
|
||||
JSONObject json = Requester.parseJSONObject(connection);
|
||||
String challenge = json.getString("challenge");
|
||||
int difficulty = json.getInt("difficulty");
|
||||
|
||||
@ -216,6 +310,7 @@ public class ReturnYouTubeDislikeApi {
|
||||
} catch (Exception ex) {
|
||||
LogHelper.printException(() -> "Failed to register user", ex);
|
||||
}
|
||||
showToast("revanced_ryd_failure_register_user");
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -230,7 +325,7 @@ public class ReturnYouTubeDislikeApi {
|
||||
}
|
||||
LogHelper.printDebug(() -> "Trying to confirm registration for user: " + userId + " with solution: " + solution);
|
||||
|
||||
HttpURLConnection connection = getConnectionFromRoute(ReturnYouTubeDislikeRoutes.CONFIRM_REGISTRATION, userId);
|
||||
HttpURLConnection connection = getRYDConnectionFromRoute(ReturnYouTubeDislikeRoutes.CONFIRM_REGISTRATION, userId);
|
||||
applyCommonPostRequestSettings(connection);
|
||||
|
||||
String jsonInputString = "{\"solution\": \"" + solution + "\"}";
|
||||
@ -240,26 +335,27 @@ public class ReturnYouTubeDislikeApi {
|
||||
}
|
||||
final int responseCode = connection.getResponseCode();
|
||||
if (checkIfRateLimitWasHit(responseCode)) {
|
||||
connection.disconnect();
|
||||
connection.disconnect(); // disconnect, as no more connections will be made for a little while
|
||||
return null;
|
||||
}
|
||||
if (responseCode == SUCCESS_HTTP_STATUS_CODE) {
|
||||
String result = Requester.parseJson(connection); // also disconnects
|
||||
String result = Requester.parseJson(connection);
|
||||
if (result.equalsIgnoreCase("true")) {
|
||||
LogHelper.printDebug(() -> "Registration confirmation successful for user: " + userId);
|
||||
return userId;
|
||||
}
|
||||
LogHelper.printDebug(() -> "Failed to confirm registration for user: " + userId
|
||||
+ " solution: " + solution + " response string was: " + result);
|
||||
return null;
|
||||
} else {
|
||||
LogHelper.printDebug(() -> "Failed to confirm registration for user: " + userId
|
||||
+ " solution: " + solution + " response code was: " + responseCode);
|
||||
}
|
||||
LogHelper.printDebug(() -> "Failed to confirm registration for user: " + userId
|
||||
+ " solution: " + solution + " response code was: " + responseCode);
|
||||
connection.disconnect();
|
||||
connection.disconnect(); // something went wrong, might as well disconnect
|
||||
} catch (Exception ex) {
|
||||
LogHelper.printException(() -> "Failed to confirm registration for user: " + userId
|
||||
+ "solution: " + solution, ex);
|
||||
}
|
||||
showToast("revanced_ryd_failure_confirm_user");
|
||||
|
||||
return null;
|
||||
}
|
||||
@ -277,7 +373,7 @@ public class ReturnYouTubeDislikeApi {
|
||||
LogHelper.printDebug(() -> "Trying to vote for video: "
|
||||
+ videoId + " with vote: " + vote + " user: " + userId);
|
||||
|
||||
HttpURLConnection connection = getConnectionFromRoute(ReturnYouTubeDislikeRoutes.SEND_VOTE);
|
||||
HttpURLConnection connection = getRYDConnectionFromRoute(ReturnYouTubeDislikeRoutes.SEND_VOTE);
|
||||
applyCommonPostRequestSettings(connection);
|
||||
|
||||
String voteJsonString = "{\"userId\": \"" + userId + "\", \"videoId\": \"" + videoId + "\", \"value\": \"" + vote.value + "\"}";
|
||||
@ -288,11 +384,11 @@ public class ReturnYouTubeDislikeApi {
|
||||
|
||||
final int responseCode = connection.getResponseCode();
|
||||
if (checkIfRateLimitWasHit(responseCode)) {
|
||||
connection.disconnect();
|
||||
connection.disconnect(); // disconnect, as no more connections will be made for a little while
|
||||
return false;
|
||||
}
|
||||
if (responseCode == SUCCESS_HTTP_STATUS_CODE) {
|
||||
JSONObject json = Requester.getJSONObject(connection); // also disconnects
|
||||
JSONObject json = Requester.parseJSONObject(connection);
|
||||
String challenge = json.getString("challenge");
|
||||
int difficulty = json.getInt("difficulty");
|
||||
|
||||
@ -301,11 +397,12 @@ public class ReturnYouTubeDislikeApi {
|
||||
}
|
||||
LogHelper.printDebug(() -> "Failed to send vote for video: " + videoId
|
||||
+ " userId: " + userId + " vote: " + vote + " response code was: " + responseCode);
|
||||
connection.disconnect();
|
||||
connection.disconnect(); // something went wrong, might as well disconnect
|
||||
} catch (Exception ex) {
|
||||
LogHelper.printException(() -> "Failed to send vote for video: " + videoId
|
||||
+ " user: " + userId + " vote: " + vote, ex);
|
||||
}
|
||||
showToast("revanced_ryd_failure_send_vote_failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -321,7 +418,7 @@ public class ReturnYouTubeDislikeApi {
|
||||
}
|
||||
LogHelper.printDebug(() -> "Trying to confirm vote for video: "
|
||||
+ videoId + " user: " + userId + " solution: " + solution);
|
||||
HttpURLConnection connection = getConnectionFromRoute(ReturnYouTubeDislikeRoutes.CONFIRM_VOTE);
|
||||
HttpURLConnection connection = getRYDConnectionFromRoute(ReturnYouTubeDislikeRoutes.CONFIRM_VOTE);
|
||||
applyCommonPostRequestSettings(connection);
|
||||
|
||||
String jsonInputString = "{\"userId\": \"" + userId + "\", \"videoId\": \"" + videoId + "\", \"solution\": \"" + solution + "\"}";
|
||||
@ -331,31 +428,36 @@ public class ReturnYouTubeDislikeApi {
|
||||
}
|
||||
final int responseCode = connection.getResponseCode();
|
||||
if (checkIfRateLimitWasHit(responseCode)) {
|
||||
connection.disconnect();
|
||||
connection.disconnect(); // disconnect, as no more connections will be made for a little while
|
||||
return false;
|
||||
}
|
||||
|
||||
if (responseCode == SUCCESS_HTTP_STATUS_CODE) {
|
||||
String result = Requester.parseJson(connection); // also disconnects
|
||||
String result = Requester.parseJson(connection);
|
||||
if (result.equalsIgnoreCase("true")) {
|
||||
LogHelper.printDebug(() -> "Vote confirm successful for video: " + videoId);
|
||||
return true;
|
||||
}
|
||||
LogHelper.printDebug(() -> "Failed to confirm vote for video: " + videoId
|
||||
+ " user: " + userId + " solution: " + solution + " response string was: " + result);
|
||||
return false;
|
||||
} else {
|
||||
LogHelper.printDebug(() -> "Failed to confirm vote for video: " + videoId
|
||||
+ " user: " + userId + " solution: " + solution + " response code was: " + responseCode);
|
||||
}
|
||||
LogHelper.printDebug(() -> "Failed to confirm vote for video: " + videoId
|
||||
+ " user: " + userId + " solution: " + solution + " response code was: " + responseCode);
|
||||
connection.disconnect();
|
||||
connection.disconnect(); // something went wrong, might as well disconnect
|
||||
} catch (Exception ex) {
|
||||
LogHelper.printException(() -> "Failed to confirm vote for video: " + videoId
|
||||
+ " user: " + userId + " solution: " + solution, ex);
|
||||
}
|
||||
showToast("revanced_ryd_failure_confirm_vote_failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// utils
|
||||
private static void showToast(String toastTextStringKey) {
|
||||
ReVancedUtils.runOnMainThread(() -> { // must show toasts on main thread
|
||||
Toast.makeText(ReVancedUtils.getContext(), str(toastTextStringKey), Toast.LENGTH_LONG).show();
|
||||
});
|
||||
}
|
||||
|
||||
private static void applyCommonPostRequestSettings(HttpURLConnection connection) throws ProtocolException {
|
||||
connection.setRequestMethod("POST");
|
||||
@ -365,15 +467,10 @@ public class ReturnYouTubeDislikeApi {
|
||||
connection.setRequestProperty("Cache-Control", "no-cache");
|
||||
connection.setUseCaches(false);
|
||||
connection.setDoOutput(true);
|
||||
connection.setConnectTimeout(API_REGISTER_VOTE_DEFAULT_TIMEOUT_MILLISECONDS); // timeout for TCP connection to server
|
||||
connection.setReadTimeout(API_REGISTER_VOTE_DEFAULT_TIMEOUT_MILLISECONDS); // timeout for server response
|
||||
connection.setConnectTimeout(API_REGISTER_VOTE_TIMEOUT_MILLISECONDS); // timeout for TCP connection to server
|
||||
connection.setReadTimeout(API_REGISTER_VOTE_TIMEOUT_MILLISECONDS); // timeout for server response
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
private static HttpURLConnection getConnectionFromRoute(Route route, String... params) throws IOException {
|
||||
return Requester.getConnectionFromRoute(RYD_API_URL, route, params);
|
||||
}
|
||||
|
||||
private static String solvePuzzle(String challenge, int difficulty) {
|
||||
final long timeSolveStarted = System.currentTimeMillis();
|
||||
|
@ -3,15 +3,26 @@ package app.revanced.integrations.returnyoutubedislike.requests;
|
||||
import static app.revanced.integrations.requests.Route.Method.GET;
|
||||
import static app.revanced.integrations.requests.Route.Method.POST;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
import app.revanced.integrations.requests.Requester;
|
||||
import app.revanced.integrations.requests.Route;
|
||||
|
||||
public class ReturnYouTubeDislikeRoutes {
|
||||
public static final Route SEND_VOTE = new Route(POST, "interact/vote");
|
||||
public static final Route CONFIRM_VOTE = new Route(POST, "interact/confirmVote");
|
||||
public static final Route GET_DISLIKES = new Route(GET, "votes?videoId={video_id}");
|
||||
public static final Route GET_REGISTRATION = new Route(GET, "puzzle/registration?userId={user_id}");
|
||||
public static final Route CONFIRM_REGISTRATION = new Route(POST, "puzzle/registration?userId={user_id}");
|
||||
class ReturnYouTubeDislikeRoutes {
|
||||
static final String RYD_API_URL = "https://returnyoutubedislikeapi.com/";
|
||||
|
||||
static final Route SEND_VOTE = new Route(POST, "interact/vote");
|
||||
static final Route CONFIRM_VOTE = new Route(POST, "interact/confirmVote");
|
||||
static final Route GET_DISLIKES = new Route(GET, "votes?videoId={video_id}");
|
||||
static final Route GET_REGISTRATION = new Route(GET, "puzzle/registration?userId={user_id}");
|
||||
static final Route CONFIRM_REGISTRATION = new Route(POST, "puzzle/registration?userId={user_id}");
|
||||
|
||||
private ReturnYouTubeDislikeRoutes() {
|
||||
}
|
||||
|
||||
static HttpURLConnection getRYDConnectionFromRoute(Route route, String... params) throws IOException {
|
||||
return Requester.getConnectionFromRoute(RYD_API_URL, route, params);
|
||||
}
|
||||
|
||||
}
|
@ -13,6 +13,7 @@ import android.preference.PreferenceScreen;
|
||||
import android.preference.SwitchPreference;
|
||||
|
||||
import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike;
|
||||
import app.revanced.integrations.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
|
||||
import app.revanced.integrations.settings.SettingsEnum;
|
||||
import app.revanced.integrations.utils.SharedPrefHelper;
|
||||
|
||||
@ -100,6 +101,85 @@ public class ReturnYouTubeDislikeSettingsFragment extends PreferenceFragment {
|
||||
return false;
|
||||
});
|
||||
preferenceScreen.addPreference(aboutWebsitePreference);
|
||||
|
||||
// RYD API connection statistics
|
||||
|
||||
if (SettingsEnum.DEBUG.getBoolean()) {
|
||||
PreferenceCategory emptyCategory = new PreferenceCategory(context); // vertical padding
|
||||
preferenceScreen.addPreference(emptyCategory);
|
||||
|
||||
PreferenceCategory statisticsCategory = new PreferenceCategory(context);
|
||||
statisticsCategory.setTitle(str("revanced_ryd_statistics_category_title"));
|
||||
preferenceScreen.addPreference(statisticsCategory);
|
||||
|
||||
Preference statisticPreference;
|
||||
|
||||
statisticPreference = new Preference(context);
|
||||
statisticPreference.setSelectable(false);
|
||||
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallResponseTimeAverage_title"));
|
||||
statisticPreference.setSummary(createMillisecondStringFromNumber(ReturnYouTubeDislikeApi.getFetchCallResponseTimeAverage()));
|
||||
preferenceScreen.addPreference(statisticPreference);
|
||||
|
||||
statisticPreference = new Preference(context);
|
||||
statisticPreference.setSelectable(false);
|
||||
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallResponseTimeMin_title"));
|
||||
statisticPreference.setSummary(createMillisecondStringFromNumber(ReturnYouTubeDislikeApi.getFetchCallResponseTimeMin()));
|
||||
preferenceScreen.addPreference(statisticPreference);
|
||||
|
||||
statisticPreference = new Preference(context);
|
||||
statisticPreference.setSelectable(false);
|
||||
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallResponseTimeMax_title"));
|
||||
statisticPreference.setSummary(createMillisecondStringFromNumber(ReturnYouTubeDislikeApi.getFetchCallResponseTimeMax()));
|
||||
preferenceScreen.addPreference(statisticPreference);
|
||||
|
||||
String fetchCallTimeWaitingLastSummary;
|
||||
final long fetchCallTimeWaitingLast = ReturnYouTubeDislikeApi.getFetchCallResponseTimeLast();
|
||||
if (fetchCallTimeWaitingLast == ReturnYouTubeDislikeApi.FETCH_CALL_RESPONSE_TIME_VALUE_RATE_LIMIT) {
|
||||
fetchCallTimeWaitingLastSummary = str("revanced_ryd_statistics_getFetchCallResponseTimeLast_rate_limit_summary");
|
||||
} else {
|
||||
fetchCallTimeWaitingLastSummary = createMillisecondStringFromNumber(fetchCallTimeWaitingLast);
|
||||
}
|
||||
statisticPreference = new Preference(context);
|
||||
statisticPreference.setSelectable(false);
|
||||
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallResponseTimeLast_title"));
|
||||
statisticPreference.setSummary(fetchCallTimeWaitingLastSummary);
|
||||
preferenceScreen.addPreference(statisticPreference);
|
||||
|
||||
statisticPreference = new Preference(context);
|
||||
statisticPreference.setSelectable(false);
|
||||
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallCount_title"));
|
||||
statisticPreference.setSummary(createSummaryText(ReturnYouTubeDislikeApi.getFetchCallCount(),
|
||||
"revanced_ryd_statistics_getFetchCallCount_zero_summary",
|
||||
"revanced_ryd_statistics_getFetchCallCount_non_zero_summary"));
|
||||
preferenceScreen.addPreference(statisticPreference);
|
||||
|
||||
statisticPreference = new Preference(context);
|
||||
statisticPreference.setSelectable(false);
|
||||
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallNumberOfFailures_title"));
|
||||
statisticPreference.setSummary(createSummaryText(ReturnYouTubeDislikeApi.getFetchCallNumberOfFailures(),
|
||||
"revanced_ryd_statistics_getFetchCallNumberOfFailures_zero_summary",
|
||||
"revanced_ryd_statistics_getFetchCallNumberOfFailures_non_zero_summary"));
|
||||
preferenceScreen.addPreference(statisticPreference);
|
||||
|
||||
statisticPreference = new Preference(context);
|
||||
statisticPreference.setSelectable(false);
|
||||
statisticPreference.setTitle(str("revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_title"));
|
||||
statisticPreference.setSummary(createSummaryText(ReturnYouTubeDislikeApi.getNumberOfRateLimitRequestsEncountered(),
|
||||
"revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_zero_summary",
|
||||
"revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_non_zero_summary"));
|
||||
preferenceScreen.addPreference(statisticPreference);
|
||||
}
|
||||
}
|
||||
|
||||
private String createSummaryText(int value, String summaryStringZeroKey, String summaryStringOneOrMoreKey) {
|
||||
if (value == 0) {
|
||||
return str(summaryStringZeroKey);
|
||||
}
|
||||
return String.format(str(summaryStringOneOrMoreKey), value);
|
||||
}
|
||||
|
||||
private static String createMillisecondStringFromNumber(long number) {
|
||||
return String.format(str("revanced_ryd_statistics_millisecond_text"), number);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -46,7 +46,11 @@ public class SBRequester {
|
||||
runVipCheck();
|
||||
|
||||
if (responseCode == 200) {
|
||||
JSONArray responseArray = Requester.getJSONArray(connection);
|
||||
// FIXME? should this use Requester#getJSONArray and not disconnect?
|
||||
// HTTPURLConnection#disconnect says:
|
||||
// disconnect if other requests to the server
|
||||
// are unlikely in the near future.
|
||||
JSONArray responseArray = Requester.parseJSONArrayAndDisconnect(connection);
|
||||
int length = responseArray.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
JSONObject obj = (JSONObject) responseArray.get(i);
|
||||
@ -96,13 +100,13 @@ public class SBRequester {
|
||||
SponsorBlockUtils.messageToToast = str("submit_failed_duplicate");
|
||||
break;
|
||||
case 403:
|
||||
SponsorBlockUtils.messageToToast = str("submit_failed_forbidden", Requester.parseErrorJson(connection));
|
||||
SponsorBlockUtils.messageToToast = str("submit_failed_forbidden", Requester.parseErrorJsonAndDisconnect(connection));
|
||||
break;
|
||||
case 429:
|
||||
SponsorBlockUtils.messageToToast = str("submit_failed_rate_limit");
|
||||
break;
|
||||
case 400:
|
||||
SponsorBlockUtils.messageToToast = str("submit_failed_invalid", Requester.parseErrorJson(connection));
|
||||
SponsorBlockUtils.messageToToast = str("submit_failed_invalid", Requester.parseErrorJsonAndDisconnect(connection));
|
||||
break;
|
||||
default:
|
||||
SponsorBlockUtils.messageToToast = str("submit_failed_unknown_error", responseCode, connection.getResponseMessage());
|
||||
@ -143,7 +147,7 @@ public class SBRequester {
|
||||
SponsorBlockUtils.messageToToast = str("vote_succeeded");
|
||||
break;
|
||||
case 403:
|
||||
SponsorBlockUtils.messageToToast = str("vote_failed_forbidden", Requester.parseErrorJson(connection));
|
||||
SponsorBlockUtils.messageToToast = str("vote_failed_forbidden", Requester.parseErrorJsonAndDisconnect(connection));
|
||||
break;
|
||||
default:
|
||||
SponsorBlockUtils.messageToToast = str("vote_failed_unknown_error", responseCode, connection.getResponseMessage());
|
||||
@ -220,6 +224,6 @@ public class SBRequester {
|
||||
}
|
||||
|
||||
private static JSONObject getJSONObject(Route route, String... params) throws Exception {
|
||||
return Requester.getJSONObject(getConnectionFromRoute(route, params));
|
||||
return Requester.parseJSONObjectAndDisconnect(getConnectionFromRoute(route, params));
|
||||
}
|
||||
}
|
||||
|
@ -53,16 +53,18 @@ public class LogHelper {
|
||||
*/
|
||||
public static void printDebug(LogMessage message) {
|
||||
if (SettingsEnum.DEBUG.getBoolean()) {
|
||||
var log = new StringBuilder(message.buildMessageString());
|
||||
var messageString = message.buildMessageString();
|
||||
|
||||
if (SettingsEnum.DEBUG_STACKTRACE.getBoolean()) {
|
||||
var builder = new StringBuilder(messageString);
|
||||
var sw = new StringWriter();
|
||||
new Throwable().printStackTrace(new PrintWriter(sw));
|
||||
|
||||
log.append(String.format("\n%s", sw));
|
||||
builder.append(String.format("\n%s", sw));
|
||||
messageString = builder.toString();
|
||||
}
|
||||
|
||||
Log.d("revanced: " + message.findOuterClassSimpleName(), log.toString());
|
||||
Log.d("revanced: " + message.findOuterClassSimpleName(), messageString);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ import android.os.Looper;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -32,13 +33,22 @@ public class ReVancedUtils {
|
||||
|
||||
/**
|
||||
* General purpose pool for network calls and other background tasks.
|
||||
* All tasks run at max thread priority.
|
||||
*/
|
||||
private static final ThreadPoolExecutor backgroundThreadPool = new ThreadPoolExecutor(
|
||||
2, // minimum 2 threads always ready to be used
|
||||
1, // minimum 1 thread always ready to be used
|
||||
10, // For any threads over the minimum, keep them alive 10 seconds after they go idle
|
||||
SHARED_THREAD_POOL_MAXIMUM_BACKGROUND_THREADS,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<Runnable>());
|
||||
new LinkedBlockingQueue<Runnable>(),
|
||||
new ThreadFactory() {
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = new Thread(r);
|
||||
t.setPriority(Thread.MAX_PRIORITY); // run at max priority
|
||||
return t;
|
||||
}
|
||||
});
|
||||
|
||||
private static void checkIfPoolHasReachedLimit() {
|
||||
if (backgroundThreadPool.getActiveCount() >= SHARED_THREAD_POOL_MAXIMUM_BACKGROUND_THREADS) {
|
||||
@ -54,13 +64,14 @@ public class ReVancedUtils {
|
||||
}
|
||||
|
||||
public static void runOnBackgroundThread(Runnable task) {
|
||||
checkIfPoolHasReachedLimit();
|
||||
backgroundThreadPool.execute(task);
|
||||
checkIfPoolHasReachedLimit();
|
||||
}
|
||||
|
||||
public static <T> Future<T> submitOnBackgroundThread(Callable<T> call) {
|
||||
Future<T> future = backgroundThreadPool.submit(call);
|
||||
checkIfPoolHasReachedLimit();
|
||||
return backgroundThreadPool.submit(call);
|
||||
return future;
|
||||
}
|
||||
|
||||
public static boolean containsAny(final String value, final String... targets) {
|
||||
@ -114,8 +125,18 @@ public class ReVancedUtils {
|
||||
return context.getResources().getConfiguration().smallestScreenWidthDp >= 600;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically logs any exceptions the runnable throws
|
||||
*/
|
||||
public static void runOnMainThread(Runnable runnable) {
|
||||
new Handler(Looper.getMainLooper()).post(runnable);
|
||||
Runnable exceptLoggingRunnable = () -> {
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Exception ex) {
|
||||
LogHelper.printException(() -> "Exception on main thread from runnable: " + runnable.toString(), ex);
|
||||
}
|
||||
};
|
||||
new Handler(Looper.getMainLooper()).post(exceptLoggingRunnable);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user