diff --git a/app/src/main/java/org/lsposed/manager/repo/RepoLoader.java b/app/src/main/java/org/lsposed/manager/repo/RepoLoader.java index bb4db3271..7530654d6 100644 --- a/app/src/main/java/org/lsposed/manager/repo/RepoLoader.java +++ b/app/src/main/java/org/lsposed/manager/repo/RepoLoader.java @@ -76,11 +76,7 @@ public boolean upgradable(long versionCode, String versionName) { private final Path repoFile = Paths.get(App.getInstance().getFilesDir().getAbsolutePath(), "repo.json"); private final Set 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 repoUrl = "https://backup.modules.lsposed.org/"; private final Resources resources = App.getInstance().getResources(); private final String[] channels = resources.getStringArray(R.array.update_channel_values); @@ -98,22 +94,28 @@ public static synchronized RepoLoader getInstance() { synchronized public void loadRemoteData() { repoLoaded = false; + boolean loaded = false; 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); - } - } + 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()); + } + try { + String bodyString = body.string(); + if (bodyString.trim().isEmpty()) { + throw new IOException("Empty response from " + response.request().url()); + } + Files.write(repoFile, bodyString.getBytes(StandardCharsets.UTF_8)); + loadLocalData(false); + loaded = true; + } catch (Throwable t) { + Log.e(App.TAG, Log.getStackTraceString(t)); + for (RepoListener listener : listeners) { + listener.onThrowable(t); } } } @@ -122,12 +124,12 @@ synchronized public void loadRemoteData() { for (RepoListener listener : listeners) { listener.onThrowable(e); } - 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(); + } } } } @@ -252,39 +254,51 @@ public void loadRemoteReleases(String packageName) { @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); - } + for (RepoListener listener : listeners) { + listener.onThrowable(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()); + for (RepoListener listener : listeners) { + listener.onThrowable(e); + } + response.close(); + 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()); + } + try { + 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); + for (RepoListener listener : listeners) { + listener.onModuleReleasesLoaded(module); } + } catch (Throwable t) { + Log.e(App.TAG, Log.getStackTraceString(t)); + for (RepoListener listener : listeners) { + listener.onThrowable(t); + } + } + } catch (Throwable t) { + Log.e(App.TAG, Log.getStackTraceString(t)); + for (RepoListener listener : listeners) { + listener.onThrowable(t); } } } diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java index 3dcab2aa2..dd54df2d8 100644 --- a/app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java @@ -109,6 +109,8 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.RepoLis OnlineModule module; private ReleaseAdapter releaseAdapter; private InformationAdapter informationAdapter; + private boolean remoteModuleLoadRequested = false; + private boolean releaseLoadRequestedByUser = false; @Nullable @Override @@ -147,6 +149,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c releaseAdapter = new ReleaseAdapter(); informationAdapter = new InformationAdapter(); RepoLoader.getInstance().addListener(this); + loadRemoteModuleIfReadmeMissing(); return binding.getRoot(); } @@ -184,7 +187,7 @@ private void renderGithubMarkdown(WebView view, @Nullable String text) { } else { direction = "ltr"; } - if (text == null) { + if (TextUtils.isEmpty(text)) { text = "
" + App.getInstance().getString(R.string.list_empty) + "
"; } if (ResourceUtils.isNightMode(getResources().getConfiguration())) { @@ -238,6 +241,43 @@ public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceReque } } + @Nullable + private OnlineModule refreshModuleFromRepo() { + if (module == null || module.getName() == null) return module; + var updatedModule = RepoLoader.getInstance().getOnlineModule(module.getName()); + if (updatedModule != null) { + module = updatedModule; + } + return module; + } + + private boolean hasReadme(@Nullable OnlineModule module) { + return module != null && (!TextUtils.isEmpty(module.getReadmeHTML()) || !TextUtils.isEmpty(module.getReadme())); + } + + private void loadRemoteModuleIfReadmeMissing() { + var currentModule = refreshModuleFromRepo(); + if (currentModule == null || currentModule.getName() == null) return; + if (remoteModuleLoadRequested || currentModule.releasesLoaded || hasReadme(currentModule)) return; + + remoteModuleLoadRequested = true; + RepoLoader.getInstance().loadRemoteReleases(currentModule.getName()); + } + + @Nullable + private String getModuleReadme() { + var currentModule = refreshModuleFromRepo(); + if (currentModule == null) return null; + String readme = currentModule.getReadmeHTML(); + if (TextUtils.isEmpty(readme)) { + readme = currentModule.getReadme(); + } + if (TextUtils.isEmpty(readme)) { + loadRemoteModuleIfReadmeMissing(); + } + return readme; + } + @Override public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) { @@ -260,20 +300,36 @@ public void onDestroyView() { binding = null; } + @Override + public void onRepoLoaded() { + var currentModule = refreshModuleFromRepo(); + if (!hasReadme(currentModule)) { + remoteModuleLoadRequested = false; + } + loadRemoteModuleIfReadmeMissing(); + if (releaseAdapter != null) { + runAsync(releaseAdapter::loadItems); + } + } + @Override public void onModuleReleasesLoaded(OnlineModule module) { + if (this.module == null || module == null || !TextUtils.equals(this.module.getName(), module.getName())) return; this.module = module; var repoLoader = RepoLoader.getInstance(); if (releaseAdapter != null) { runAsync(releaseAdapter::loadItems); } - if ((repoLoader.getReleases(module.getName()) != null ? repoLoader.getReleases(module.getName()).size() : 1) == 1) { + if (releaseLoadRequestedByUser && (repoLoader.getReleases(module.getName()) != null ? repoLoader.getReleases(module.getName()).size() : 1) == 1) { showHint(R.string.module_release_no_more, true); } + releaseLoadRequestedByUser = false; } @Override public void onThrowable(Throwable t) { + remoteModuleLoadRequested = false; + releaseLoadRequestedByUser = false; if (releaseAdapter != null) { runAsync(releaseAdapter::loadItems); } @@ -456,6 +512,7 @@ public void onBindViewHolder(@NonNull ReleaseAdapter.ViewHolder holder, int posi if (holder.progress.getVisibility() == View.GONE) { holder.title.setVisibility(View.GONE); holder.progress.show(); + releaseLoadRequestedByUser = true; RepoLoader.getInstance().loadRemoteReleases(module.getName()); } }); @@ -611,9 +668,17 @@ public void onPause() { } } - public static class ReadmeFragment extends BorderFragment { + public static class ReadmeFragment extends BorderFragment implements RepoLoader.RepoListener { ItemRepoReadmeBinding binding; + private void renderReadme() { + var parent = getParentFragment(); + if (!(parent instanceof RepoItemFragment) || binding == null) return; + + var repoItemFragment = (RepoItemFragment) parent; + repoItemFragment.renderGithubMarkdown(binding.readme, repoItemFragment.getModuleReadme()); + } + @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { @@ -624,13 +689,40 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c } return null; } - var repoItemFragment = (RepoItemFragment) parent; binding = ItemRepoReadmeBinding.inflate(getLayoutInflater(), container, false); - repoItemFragment.renderGithubMarkdown(binding.readme, repoItemFragment.module.getReadmeHTML()); + renderReadme(); borderView = binding.scrollView; + RepoLoader.getInstance().addListener(this); return binding.getRoot(); } + @Override + public void onRepoLoaded() { + if (binding != null) { + runOnUiThread(this::renderReadme); + } + } + + @Override + public void onModuleReleasesLoaded(OnlineModule module) { + if (binding != null) { + var parent = getParentFragment(); + if (parent instanceof RepoItemFragment) { + var repoItemFragment = (RepoItemFragment) parent; + if (repoItemFragment.module != null && TextUtils.equals(repoItemFragment.module.getName(), module.getName())) { + runOnUiThread(this::renderReadme); + } + } + } + } + + @Override + public void onDestroyView() { + RepoLoader.getInstance().removeListener(this); + binding = null; + super.onDestroyView(); + } + @Override void scrollToTop() { binding.scrollView.fullScroll(ScrollView.FOCUS_UP);