-
Notifications
You must be signed in to change notification settings - Fork 2
Fork review for upstream #765: module repository loading #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
a5efce5
5f247d9
e00bab5
dbb0963
d08a313
f4ad016
83553ab
74f60ae
145ea21
7db9055
f53337f
42c19a8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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); | ||
|
|
||
|
|
@@ -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]); | ||
| updateLatestVersion(repoModules, channel); | ||
| onlineModules = modules; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In the successful Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| 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; | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -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) { | ||
|
|
@@ -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); | ||
|
Shallow-dusty marked this conversation as resolved.
|
||
| } | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| 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); | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.