Skip to content

Commit 0b0305e

Browse files
authored
Merge pull request #5474 from XiangRongLin/expires_header
Respect expires header when checking for new versions
2 parents 950997e + bdc85b4 commit 0b0305e

4 files changed

Lines changed: 143 additions & 25 deletions

File tree

app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,19 @@
1010
import android.net.ConnectivityManager;
1111
import android.net.Uri;
1212
import android.util.Log;
13-
1413
import androidx.annotation.NonNull;
1514
import androidx.annotation.Nullable;
1615
import androidx.core.app.NotificationCompat;
1716
import androidx.core.app.NotificationManagerCompat;
1817
import androidx.core.content.ContextCompat;
1918
import androidx.preference.PreferenceManager;
20-
2119
import com.grack.nanojson.JsonObject;
2220
import com.grack.nanojson.JsonParser;
2321
import com.grack.nanojson.JsonParserException;
24-
25-
import org.schabi.newpipe.report.ErrorActivity;
26-
import org.schabi.newpipe.report.ErrorInfo;
27-
import org.schabi.newpipe.report.UserAction;
28-
22+
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
23+
import io.reactivex.rxjava3.core.Maybe;
24+
import io.reactivex.rxjava3.disposables.Disposable;
25+
import io.reactivex.rxjava3.schedulers.Schedulers;
2926
import java.io.ByteArrayInputStream;
3027
import java.io.InputStream;
3128
import java.security.MessageDigest;
@@ -34,11 +31,9 @@
3431
import java.security.cert.CertificateException;
3532
import java.security.cert.CertificateFactory;
3633
import java.security.cert.X509Certificate;
37-
38-
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
39-
import io.reactivex.rxjava3.core.Maybe;
40-
import io.reactivex.rxjava3.disposables.Disposable;
41-
import io.reactivex.rxjava3.schedulers.Schedulers;
34+
import org.schabi.newpipe.report.ErrorActivity;
35+
import org.schabi.newpipe.report.ErrorInfo;
36+
import org.schabi.newpipe.report.UserAction;
4237

4338
public final class CheckForNewAppVersion {
4439
private CheckForNewAppVersion() { }
@@ -176,38 +171,60 @@ public static boolean isGithubApk(@NonNull final App app) {
176171
@Nullable
177172
public static Disposable checkNewVersion(@NonNull final App app) {
178173
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
174+
final NewVersionManager manager = new NewVersionManager();
179175

180176
// Check if user has enabled/disabled update checking
181177
// and if the current apk is a github one or not.
182178
if (!prefs.getBoolean(app.getString(R.string.update_app_key), true) || !isGithubApk(app)) {
183179
return null;
184180
}
185181

182+
// Check if the last request has happened a certain time ago
183+
// to reduce the number of API requests.
184+
final long expiry = prefs.getLong(app.getString(R.string.update_expiry_key), 0);
185+
if (!manager.isExpired(expiry)) {
186+
return null;
187+
}
188+
186189
return Maybe
187-
.fromCallable(() -> {
188-
if (!isConnected(app)) {
189-
return null;
190-
}
191-
192-
// Make a network request to get latest NewPipe data.
193-
return DownloaderImpl.getInstance().get(NEWPIPE_API_URL).responseBody();
194-
})
190+
.fromCallable(() -> {
191+
if (!isConnected(app)) {
192+
return null;
193+
}
194+
195+
// Make a network request to get latest NewPipe data.
196+
return DownloaderImpl.getInstance().get(NEWPIPE_API_URL);
197+
})
195198
.subscribeOn(Schedulers.io())
196199
.observeOn(AndroidSchedulers.mainThread())
197200
.subscribe(
198201
response -> {
202+
try {
203+
// Store a timestamp which needs to be exceeded,
204+
// before a new request to the API is made.
205+
final long newExpiry = manager
206+
.coerceExpiry(response.getHeader("expires"));
207+
prefs.edit()
208+
.putLong(app.getString(R.string.update_expiry_key), newExpiry)
209+
.apply();
210+
} catch (final Exception e) {
211+
if (DEBUG) {
212+
Log.w(TAG, "Could not extract and save new expiry date", e);
213+
}
214+
}
215+
199216
// Parse the json from the response.
200217
try {
201218
final JsonObject githubStableObject = JsonParser.object()
202-
.from(response).getObject("flavors").getObject("github")
203-
.getObject("stable");
219+
.from(response.responseBody()).getObject("flavors")
220+
.getObject("github").getObject("stable");
204221

205222
final String versionName = githubStableObject
206-
.getString("version");
223+
.getString("version");
207224
final int versionCode = githubStableObject
208-
.getInt("version_code");
225+
.getInt("version_code");
209226
final String apkLocationUrl = githubStableObject
210-
.getString("apk");
227+
.getString("apk");
211228

212229
compareAppVersionAndShowNotification(app, versionName,
213230
apkLocationUrl, versionCode);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.schabi.newpipe
2+
3+
import java.time.Instant
4+
import java.time.ZonedDateTime
5+
import java.time.format.DateTimeFormatter
6+
7+
class NewVersionManager {
8+
9+
fun isExpired(expiry: Long): Boolean {
10+
return Instant.ofEpochSecond(expiry).isBefore(Instant.now())
11+
}
12+
13+
/**
14+
* Coerce expiry date time in between 6 hours and 72 hours from now
15+
*
16+
* @return Epoch second of expiry date time
17+
*/
18+
fun coerceExpiry(expiryString: String?): Long {
19+
val now = ZonedDateTime.now()
20+
return expiryString?.let {
21+
22+
var expiry = ZonedDateTime.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(expiryString))
23+
expiry = maxOf(expiry, now.plusHours(6))
24+
expiry = minOf(expiry, now.plusHours(72))
25+
expiry.toEpochSecond()
26+
} ?: now.plusHours(6).toEpochSecond()
27+
}
28+
}

app/src/main/res/values/settings_keys.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@
342342
<!-- Updates -->
343343
<string name="update_app_key" translatable="false">update_app_key</string>
344344
<string name="update_pref_screen_key" translatable="false">update_pref_screen_key</string>
345+
<string name="update_expiry_key" translatable="false">update_expiry_key</string>
345346

346347
<!-- Localizations -->
347348
<string name="default_localization_key" translatable="false">system</string>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package org.schabi.newpipe
2+
3+
import org.junit.Assert.assertFalse
4+
import org.junit.Assert.assertTrue
5+
import org.junit.Before
6+
import org.junit.Test
7+
import java.time.Instant
8+
import java.time.ZoneId
9+
import java.time.format.DateTimeFormatter
10+
import kotlin.math.abs
11+
12+
class NewVersionManagerTest {
13+
14+
private lateinit var manager: NewVersionManager
15+
16+
@Before
17+
fun setup() {
18+
manager = NewVersionManager()
19+
}
20+
21+
@Test
22+
fun `Expiry is reached`() {
23+
val oneHourEarlier = Instant.now().atZone(ZoneId.of("GMT")).minusHours(1)
24+
25+
val expired = manager.isExpired(oneHourEarlier.toEpochSecond())
26+
27+
assertTrue(expired)
28+
}
29+
30+
@Test
31+
fun `Expiry is not reached`() {
32+
val oneHourLater = Instant.now().atZone(ZoneId.of("GMT")).plusHours(1)
33+
34+
val expired = manager.isExpired(oneHourLater.toEpochSecond())
35+
36+
assertFalse(expired)
37+
}
38+
39+
/**
40+
* Equal within a range of 5 seconds
41+
*/
42+
private fun assertNearlyEqual(a: Long, b: Long) {
43+
assertTrue(abs(a - b) < 5)
44+
}
45+
46+
@Test
47+
fun `Expiry must be returned as is because it is inside the acceptable range of 6-72 hours`() {
48+
val sixHoursLater = Instant.now().atZone(ZoneId.of("GMT")).plusHours(6)
49+
50+
val coerced = manager.coerceExpiry(DateTimeFormatter.RFC_1123_DATE_TIME.format(sixHoursLater))
51+
52+
assertNearlyEqual(sixHoursLater.toEpochSecond(), coerced)
53+
}
54+
55+
@Test
56+
fun `Expiry must be increased to 6 hours if below`() {
57+
val tooLow = Instant.now().atZone(ZoneId.of("GMT")).plusHours(5)
58+
59+
val coerced = manager.coerceExpiry(DateTimeFormatter.RFC_1123_DATE_TIME.format(tooLow))
60+
61+
assertNearlyEqual(tooLow.plusHours(1).toEpochSecond(), coerced)
62+
}
63+
64+
@Test
65+
fun `Expiry must be decreased to 72 hours if above`() {
66+
val tooHigh = Instant.now().atZone(ZoneId.of("GMT")).plusHours(73)
67+
68+
val coerced = manager.coerceExpiry(DateTimeFormatter.RFC_1123_DATE_TIME.format(tooHigh))
69+
70+
assertNearlyEqual(tooHigh.minusHours(1).toEpochSecond(), coerced)
71+
}
72+
}

0 commit comments

Comments
 (0)