feat(plugins): cache available plugin list

This commit is contained in:
Skylot
2023-09-26 21:26:01 +01:00
parent b70276d896
commit 89acf73010
5 changed files with 151 additions and 54 deletions
@@ -3,18 +3,22 @@ package jadx.plugins.tools;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import jadx.api.plugins.utils.ZipSecurity;
import jadx.core.utils.files.FileUtils;
import jadx.plugins.tools.data.JadxPluginListCache;
import jadx.plugins.tools.data.JadxPluginMetadata;
import jadx.plugins.tools.resolvers.github.GithubTools;
import jadx.plugins.tools.resolvers.github.LocationInfo;
@@ -22,6 +26,8 @@ import jadx.plugins.tools.resolvers.github.data.Asset;
import jadx.plugins.tools.resolvers.github.data.Release;
import jadx.plugins.tools.utils.PluginUtils;
import static jadx.plugins.tools.utils.PluginFiles.PLUGINS_LIST_CACHE;
/**
* TODO: implement list caching (on disk) with check for new release
*/
@@ -31,32 +37,96 @@ public class JadxPluginsList {
private static final Type LIST_TYPE = new TypeToken<List<JadxPluginMetadata>>() {
}.getType();
private static final Type CACHE_TYPE = new TypeToken<JadxPluginListCache>() {
}.getType();
public static JadxPluginsList getInstance() {
return INSTANCE;
}
private @Nullable List<JadxPluginMetadata> cache;
private @Nullable JadxPluginListCache loadedList;
private JadxPluginsList() {
}
public synchronized List<JadxPluginMetadata> fetch() {
if (cache != null) {
return cache;
/**
* List provider with update callback.
* Can be called one or two times:
* <br>
* - Apply cached data first
* <br>
* - If update is available, apply data after fetch
* <br>
* Method call is blocking.
*/
public synchronized void get(Consumer<List<JadxPluginMetadata>> consumer) {
if (loadedList != null) {
consumer.accept(loadedList.getList());
return;
}
JadxPluginListCache listCache = loadCache();
if (listCache != null) {
consumer.accept(listCache.getList());
}
Release release = fetchLatestRelease();
if (listCache == null || !listCache.getVersion().equals(release.getName())) {
JadxPluginListCache updatedList = fetchBundle(release);
saveCache(updatedList);
consumer.accept(updatedList.getList());
}
}
public List<JadxPluginMetadata> get() {
AtomicReference<List<JadxPluginMetadata>> holder = new AtomicReference<>();
get(holder::set);
return holder.get();
}
private @Nullable JadxPluginListCache loadCache() {
if (!Files.isRegularFile(PLUGINS_LIST_CACHE)) {
return null;
}
try {
String jsonStr = FileUtils.readFile(PLUGINS_LIST_CACHE);
return buildGson().fromJson(jsonStr, CACHE_TYPE);
} catch (Exception e) {
return null;
}
}
private void saveCache(JadxPluginListCache listCache) {
try {
String jsonStr = buildGson().toJson(listCache, CACHE_TYPE);
FileUtils.writeFile(PLUGINS_LIST_CACHE, jsonStr);
} catch (Exception e) {
throw new RuntimeException("Error saving file: " + PLUGINS_LIST_CACHE, e);
}
loadedList = listCache;
}
private static Gson buildGson() {
return new GsonBuilder().setPrettyPrinting().create();
}
private Release fetchLatestRelease() {
LocationInfo latest = new LocationInfo("jadx-decompiler", "jadx-plugins-list", "list", null);
Release release = GithubTools.fetchRelease(latest);
List<Asset> assets = release.getAssets();
if (assets.isEmpty()) {
throw new RuntimeException("Release don't have assets");
}
Asset listAsset = assets.get(0);
return release;
}
private JadxPluginListCache fetchBundle(Release release) {
Asset listAsset = release.getAssets().get(0);
Path tmpListFile = FileUtils.createTempFile("list.zip");
PluginUtils.downloadFile(listAsset.getDownloadUrl(), tmpListFile);
List<JadxPluginMetadata> entries = loadListBundle(tmpListFile);
cache = entries;
return entries;
JadxPluginListCache listCache = new JadxPluginListCache();
listCache.setVersion(release.getName());
listCache.setList(loadListBundle(tmpListFile));
return listCache;
}
private static List<JadxPluginMetadata> loadListBundle(Path tmpListFile) {
@@ -73,14 +143,4 @@ public class JadxPluginsList {
});
return entries;
}
@TestOnly
public synchronized List<JadxPluginMetadata> fetchFromLocalBundle(Path bundleFile) {
if (cache != null) {
return cache;
}
List<JadxPluginMetadata> entries = loadListBundle(bundleFile);
cache = entries;
return entries;
}
}
@@ -0,0 +1,24 @@
package jadx.plugins.tools.data;
import java.util.List;
public class JadxPluginListCache {
private String version;
private List<JadxPluginMetadata> list;
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public List<JadxPluginMetadata> getList() {
return list;
}
public void setList(List<JadxPluginMetadata> list) {
this.list = list;
}
}
@@ -10,12 +10,13 @@ import static jadx.core.utils.files.FileUtils.makeDirs;
public class PluginFiles {
private static final ProjectDirectories DIRS = ProjectDirectories.from("io.github", "skylot", "jadx");
public static final Path PLUGINS_DIR = Paths.get(DIRS.configDir, "plugins");
private static final Path PLUGINS_DIR = Paths.get(DIRS.configDir, "plugins");
public static final Path PLUGINS_JSON = PLUGINS_DIR.resolve("plugins.json");
public static final Path INSTALLED_DIR = PLUGINS_DIR.resolve("installed");
public static final Path DROPINS_DIR = PLUGINS_DIR.resolve("dropins");
public static final Path CACHE_DIR = Paths.get(DIRS.cacheDir);
private static final Path CACHE_DIR = Paths.get(DIRS.cacheDir);
public static final Path PLUGINS_LIST_CACHE = CACHE_DIR.resolve("plugin-list.json");
static {
makeDirs(INSTALLED_DIR);