Skip to content
191 changes: 124 additions & 67 deletions app/src/main/java/org/lsposed/manager/repo/RepoLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,13 @@ public boolean upgradable(long versionCode, String versionName) {
private final Path repoFile = Paths.get(App.getInstance().getFilesDir().getAbsolutePath(), "repo.json");
private final Set<RepoListener> listeners = ConcurrentHashMap.newKeySet();
private boolean repoLoaded = false;
private static final String originRepoUrl = "https://modules.lsposed.org/";
private static final String backupRepoUrl = "https://modules-blogcdn.lsposed.org/";

private static final String secondBackupRepoUrl = "https://modules-cloudflare.lsposed.org/";
private static String repoUrl = originRepoUrl;
private static final String[] repoUrls = new String[]{
"https://backup.modules.lsposed.org/",
"https://modules.lsposed.org/",
"https://modules-blogcdn.lsposed.org/",
"https://modules-cloudflare.lsposed.org/"
};
private static String repoUrl = repoUrls[0];
private final Resources resources = App.getInstance().getResources();
private final String[] channels = resources.getStringArray(R.array.update_channel_values);

Expand All @@ -98,37 +100,69 @@ public static synchronized RepoLoader getInstance() {

synchronized public void loadRemoteData() {
repoLoaded = false;
boolean loaded = false;
Throwable lastError = null;
try {
try (var response = App.getOkHttpClient().newCall(new Request.Builder().url(repoUrl + "modules.json").build()).execute()) {

if (response.isSuccessful()) {
ResponseBody body = response.body();
if (body != null) {
try {
String bodyString = body.string();
Files.write(repoFile, bodyString.getBytes(StandardCharsets.UTF_8));
loadLocalData(false);
} catch (Throwable t) {
Log.e(App.TAG, Log.getStackTraceString(t));
for (RepoListener listener : listeners) {
listener.onThrowable(t);
}
}
}
for (String candidateRepoUrl : repoUrls) {
try {
String bodyString = requestString(candidateRepoUrl + "modules.json");
OnlineModule[] repoModules = parseRepoModules(bodyString);
Files.write(repoFile, bodyString.getBytes(StandardCharsets.UTF_8));
repoUrl = candidateRepoUrl;
replaceRepoModules(repoModules);
loaded = true;
break;
} catch (Throwable t) {
lastError = t;
Log.e(App.TAG, "load remote data from " + candidateRepoUrl, t);
}
}
} catch (Throwable e) {
Log.e(App.TAG, "load remote data", e);
for (RepoListener listener : listeners) {
listener.onThrowable(e);
if (!loaded && lastError != null) {
for (RepoListener listener : listeners) {
listener.onThrowable(lastError);
}
}
if (repoUrl.equals(originRepoUrl)) {
repoUrl = backupRepoUrl;
loadRemoteData();
} else if (repoUrl.equals(backupRepoUrl)) {
repoUrl = secondBackupRepoUrl;
loadRemoteData();
} finally {
if (!loaded) {
repoLoaded = true;
for (RepoListener listener : listeners) {
listener.onRepoLoaded();
}
}
}
}

private OnlineModule[] parseRepoModules(String bodyString) throws IOException {
Gson gson = new Gson();
OnlineModule[] repoModules = gson.fromJson(bodyString, OnlineModule[].class);
if (repoModules == null) {
throw new IOException("Invalid repo response");
}
return repoModules;
}

private void replaceRepoModules(OnlineModule[] repoModules) {
Map<String, OnlineModule> modules = new HashMap<>();
Arrays.stream(repoModules).forEach(onlineModule -> modules.put(onlineModule.getName(), onlineModule));
var channel = App.getPreferences().getString("update_channel", channels[0]);
onlineModules = modules;
updateLatestVersion(repoModules, channel);
}

private String requestString(String url) throws IOException {
try (var response = App.getOkHttpClient().newCall(new Request.Builder().url(url).build()).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected response " + response.code() + " from " + response.request().url());
}
ResponseBody body = response.body();
if (body == null) {
throw new IOException("Empty response from " + response.request().url());
}
String bodyString = body.string();
if (bodyString.trim().isEmpty()) {
throw new IOException("Empty response from " + response.request().url());
}
return bodyString;
}
}

Expand All @@ -141,13 +175,8 @@ synchronized public void loadLocalData(boolean updateRemoteRepo) {
}
byte[] encoded = Files.readAllBytes(repoFile);
String bodyString = new String(encoded, StandardCharsets.UTF_8);
Gson gson = new Gson();
Map<String, OnlineModule> modules = new HashMap<>();
OnlineModule[] repoModules = gson.fromJson(bodyString, OnlineModule[].class);
Arrays.stream(repoModules).forEach(onlineModule -> modules.put(onlineModule.getName(), onlineModule));
var channel = App.getPreferences().getString("update_channel", channels[0]);
updateLatestVersion(repoModules, channel);
onlineModules = modules;
OnlineModule[] repoModules = parseRepoModules(bodyString);
replaceRepoModules(repoModules);
} catch (Throwable t) {
Log.e(App.TAG, Log.getStackTraceString(t));
for (RepoListener listener : listeners) {
Expand Down Expand Up @@ -248,49 +277,77 @@ else if (module.getLatestBetaReleaseTime() != null)
}

public void loadRemoteReleases(String packageName) {
App.getOkHttpClient().newCall(new Request.Builder().url(String.format(repoUrl + "module/%s.json", packageName)).build()).enqueue(new Callback() {
loadRemoteReleases(packageName, repoUrlIndex(repoUrl), 0);
}

private int repoUrlIndex(String url) {
for (int i = 0; i < repoUrls.length; i++) {
if (repoUrls[i].equals(url)) {
return i;
}
}
return 0;
}

private int nextRepoUrlIndex(int repoUrlIndex) {
return (repoUrlIndex + 1) % repoUrls.length;
}

private void loadRemoteReleases(String packageName, int repoUrlIndex, int attempts) {
String candidateRepoUrl = repoUrls[repoUrlIndex];
App.getOkHttpClient().newCall(new Request.Builder().url(String.format(candidateRepoUrl + "module/%s.json", packageName)).build()).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
Log.e(App.TAG, call.request().url() + e.getMessage());
if (repoUrl.equals(originRepoUrl)) {
repoUrl = backupRepoUrl;
loadRemoteReleases(packageName);
} else if (repoUrl.equals(backupRepoUrl)) {
repoUrl = secondBackupRepoUrl;
loadRemoteReleases(packageName);
} else {
for (RepoListener listener : listeners) {
listener.onThrowable(e);
}
}
retryRemoteReleases(packageName, repoUrlIndex, attempts, e);
}

@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
if (response.isSuccessful()) {
if (!response.isSuccessful()) {
var e = new IOException("Unexpected response " + response.code() + " from " + call.request().url());
response.close();
retryRemoteReleases(packageName, repoUrlIndex, attempts, e);
return;
}
try (response) {
ResponseBody body = response.body();
if (body != null) {
try {
String bodyString = body.string();
Gson gson = new Gson();
OnlineModule module = gson.fromJson(bodyString, OnlineModule.class);
module.releasesLoaded = true;
onlineModules.replace(packageName, module);
for (RepoListener listener : listeners) {
listener.onModuleReleasesLoaded(module);
}
} catch (Throwable t) {
Log.e(App.TAG, Log.getStackTraceString(t));
for (RepoListener listener : listeners) {
listener.onThrowable(t);
}
}
if (body == null) {
throw new IOException("Empty response from " + call.request().url());
}
String bodyString = body.string();
if (bodyString.trim().isEmpty()) {
throw new IOException("Empty response from " + call.request().url());
}
Gson gson = new Gson();
OnlineModule module = gson.fromJson(bodyString, OnlineModule.class);
if (module == null) {
throw new IOException("Invalid response from " + call.request().url());
}
module.releasesLoaded = true;
onlineModules.replace(packageName, module);
repoUrl = candidateRepoUrl;
for (RepoListener listener : listeners) {
listener.onModuleReleasesLoaded(module);
}
} catch (Throwable t) {
Log.e(App.TAG, Log.getStackTraceString(t));
retryRemoteReleases(packageName, repoUrlIndex, attempts, t);
}
}
});
}

private void retryRemoteReleases(String packageName, int repoUrlIndex, int attempts, Throwable error) {
if (attempts + 1 < repoUrls.length) {
loadRemoteReleases(packageName, nextRepoUrlIndex(repoUrlIndex), attempts + 1);
} else {
for (RepoListener listener : listeners) {
listener.onThrowable(error);
}
}
}

public void addListener(RepoListener listener) {
listeners.add(listener);
}
Expand Down
Loading