You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
FoxMagiskModuleManager/app/src/main/java/com/fox2code/mmm/repo/RepoData.java

275 lines
12 KiB
Java

3 years ago
package com.fox2code.mmm.repo;
2 years ago
import android.annotation.SuppressLint;
3 years ago
import android.content.SharedPreferences;
2 years ago
import android.util.Log;
3 years ago
import androidx.annotation.NonNull;
import com.fox2code.mmm.R;
3 years ago
import com.fox2code.mmm.manager.ModuleInfo;
import com.fox2code.mmm.utils.Files;
2 years ago
import com.fox2code.mmm.utils.Http;
3 years ago
import com.fox2code.mmm.utils.PropUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
2 years ago
import java.io.IOException;
3 years ago
import java.nio.charset.StandardCharsets;
2 years ago
import java.text.ParseException;
import java.text.SimpleDateFormat;
3 years ago
import java.util.ArrayList;
2 years ago
import java.util.Collections;
3 years ago
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
2 years ago
import java.util.Map;
import java.util.Objects;
3 years ago
public class RepoData {
private static final String TAG = "RepoData";
3 years ago
private final Object populateLock = new Object();
public final String url;
public final File cacheRoot;
public final SharedPreferences cachedPreferences;
public final File metaDataCache;
2 years ago
public final boolean special;
3 years ago
public final HashMap<String, RepoModule> moduleHashMap;
public long lastUpdate;
public String name;
private final Map<String, SpecialData> specialData;
2 years ago
private long specialLastUpdate;
3 years ago
protected RepoData(String url, File cacheRoot, SharedPreferences cachedPreferences) {
2 years ago
this(url, cacheRoot, cachedPreferences, false);
}
RepoData(String url, File cacheRoot, SharedPreferences cachedPreferences,boolean special) {
3 years ago
this.url = url;
this.cacheRoot = cacheRoot;
this.cachedPreferences = cachedPreferences;
this.metaDataCache = new File(cacheRoot, "modules.json");
2 years ago
this.special = special;
3 years ago
this.moduleHashMap = new HashMap<>();
this.name = this.url; // Set url as default name
this.specialData = special ? new HashMap<>() : Collections.emptyMap();
3 years ago
if (!this.cacheRoot.isDirectory()) {
this.cacheRoot.mkdirs();
2 years ago
} else {
if (special) { // Special times need to be loaded before populate
File metaDataCacheSpecial = new File(cacheRoot, "modules_times.json");
if (metaDataCacheSpecial.exists()) {
try {
JSONArray jsonArray = new JSONArray(new String(
Files.read(this.metaDataCache), StandardCharsets.UTF_8));
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
this.specialData.put(
jsonObject.getString("name"), new SpecialData(
Objects.requireNonNull(ISO_OFFSET_DATE_TIME.parse(
jsonObject.getString(
"pushed_at"))).getTime(),
jsonObject.optInt("stargazers_count")));
Log.d(TAG, "Got " +
2 years ago
jsonObject.getString("name") + " from local storage!");
}
this.specialLastUpdate = metaDataCacheSpecial.lastModified();
if (this.specialLastUpdate > System.currentTimeMillis()) {
this.specialLastUpdate = 0; // Don't allow time travel
}
} catch (Exception e) {
metaDataCacheSpecial.delete();
3 years ago
}
}
2 years ago
}
if (this.metaDataCache.exists()) {
this.lastUpdate = metaDataCache.lastModified();
if (this.lastUpdate > System.currentTimeMillis()) {
this.lastUpdate = 0; // Don't allow time travel
}
2 years ago
try {
List<RepoModule> modules = this.populate(new JSONObject(
new String(Files.read(this.metaDataCache), StandardCharsets.UTF_8)));
for (RepoModule repoModule : modules) {
if (!this.tryLoadMetadata(repoModule)) {
repoModule.moduleInfo.flags &= ~ModuleInfo.FLAG_METADATA_INVALID;
}
}
} catch (Exception e) {
this.metaDataCache.delete();
}
3 years ago
}
}
}
protected boolean prepare() {
return true;
}
protected List<RepoModule> populate(JSONObject jsonObject) throws JSONException {
3 years ago
List<RepoModule> newModules = new ArrayList<>();
synchronized (this.populateLock) {
String name = jsonObject.getString("name").trim();
String nameForModules = name.endsWith(" (Official)") ?
name.substring(0, name.length() - 11) : name;
3 years ago
long lastUpdate = jsonObject.getLong("last_update");
for (RepoModule repoModule : this.moduleHashMap.values()) {
repoModule.processed = false;
}
JSONArray array = jsonObject.getJSONArray("modules");
int len = array.length();
for (int i = 0; i < len; i++) {
JSONObject module = array.getJSONObject(i);
String moduleId = module.getString("id");
// Deny remote modules ids shorter than 3 chars long or that start with a digit
if (moduleId.length() < 3 || Character.isDigit(moduleId.charAt(0))) continue;
SpecialData moduleSpecialData = this.specialData.get(moduleId);
3 years ago
long moduleLastUpdate = module.getLong("last_update");
String moduleNotesUrl = module.getString("notes_url");
String modulePropsUrl = module.getString("prop_url");
String moduleZipUrl = module.getString("zip_url");
String moduleChecksum = module.optString("checksum");
String moduleStars = module.optString("stars");
if (moduleSpecialData != null) { // Fix last update time
moduleLastUpdate = Math.max(moduleLastUpdate, moduleSpecialData.time);
2 years ago
moduleNotesUrl = Http.updateLink(moduleNotesUrl);
modulePropsUrl = Http.updateLink(modulePropsUrl);
moduleZipUrl = Http.updateLink(moduleZipUrl);
}
3 years ago
RepoModule repoModule = this.moduleHashMap.get(moduleId);
if (repoModule == null) {
repoModule = new RepoModule(moduleId);
this.moduleHashMap.put(moduleId, repoModule);
newModules.add(repoModule);
} else {
if (repoModule.lastUpdated < moduleLastUpdate ||
repoModule.moduleInfo.hasFlag(ModuleInfo.FLAG_METADATA_INVALID)) {
newModules.add(repoModule);
}
}
repoModule.processed = true;
repoModule.repoName = nameForModules;
3 years ago
repoModule.lastUpdated = moduleLastUpdate;
repoModule.notesUrl = moduleNotesUrl;
repoModule.propUrl = modulePropsUrl;
repoModule.zipUrl = moduleZipUrl;
repoModule.checksum = moduleChecksum;
if (moduleSpecialData != null) {
repoModule.qualityValue = moduleSpecialData.stars;
repoModule.qualityText = R.string.module_stars;
} else if (!moduleStars.isEmpty()) {
try {
repoModule.qualityValue = Integer.parseInt(moduleStars);
repoModule.qualityText = R.string.module_stars;
} catch (NumberFormatException ignored) {}
}
3 years ago
}
// Remove no longer existing modules
Iterator<RepoModule> moduleInfoIterator = this.moduleHashMap.values().iterator();
while (moduleInfoIterator.hasNext()) {
RepoModule repoModule = moduleInfoIterator.next();
if (!repoModule.processed) {
new File(this.cacheRoot, repoModule.id + ".prop").delete();
moduleInfoIterator.remove();
}
}
// Update final metadata
this.name = name;
this.lastUpdate = lastUpdate;
}
return newModules;
}
2 years ago
public void storeMetadata(RepoModule repoModule,byte[] data) throws IOException {
Files.write(new File(this.cacheRoot, repoModule.id + ".prop"), data);
}
3 years ago
public boolean tryLoadMetadata(RepoModule repoModule) {
File file = new File(this.cacheRoot, repoModule.id + ".prop");
if (file.exists()) {
try {
ModuleInfo moduleInfo = repoModule.moduleInfo;
2 years ago
PropUtils.readProperties(moduleInfo, file.getAbsolutePath(),
repoModule.repoName + "/" + moduleInfo.name, false);
moduleInfo.flags &= ~ModuleInfo.FLAG_METADATA_INVALID;
if (moduleInfo.version == null) {
moduleInfo.version = "v" + moduleInfo.versionCode;
}
SpecialData moduleSpecialData =
this.specialData.get(repoModule.id);
if (moduleSpecialData != null) {
repoModule.qualityValue = moduleSpecialData.stars;
repoModule.qualityText = R.string.module_stars;
}
3 years ago
return true;
} catch (Exception ignored) {
file.delete();
}
}
repoModule.moduleInfo.flags |= ModuleInfo.FLAG_METADATA_INVALID;
return false;
}
2 years ago
@SuppressLint("SimpleDateFormat")
private static final SimpleDateFormat ISO_OFFSET_DATE_TIME =
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
public void updateSpecialTimes(boolean force) throws IOException, JSONException {
if (!this.special) return;
synchronized (this.populateLock) {
if (this.specialLastUpdate == 0L || (force && (this.specialData.isEmpty() ||
this.specialLastUpdate < System.currentTimeMillis() - 60000L))) {
2 years ago
File metaDataCacheSpecial = new File(cacheRoot, "modules_times.json");
this.specialData.clear();
2 years ago
try {
// Requesting only 32 most recently pushed repos
2 years ago
byte[] data = Http.doHttpGet(
"https://api.github.com/users/Magisk-Modules-Repo/" +
"repos?sort=pushed&per_page=32", false);
2 years ago
JSONArray jsonArray = new JSONArray(new String(data, StandardCharsets.UTF_8));
for (int i = 0;i < jsonArray.length();i++) {
JSONObject jsonObject = jsonArray.optJSONObject(i);
this.specialData.put(
jsonObject.getString("name"), new SpecialData(
Objects.requireNonNull(ISO_OFFSET_DATE_TIME.parse(
jsonObject.getString(
"pushed_at"))).getTime(),
jsonObject.optInt("stargazers_count")));
2 years ago
}
Files.write(metaDataCacheSpecial, data);
this.specialLastUpdate = System.currentTimeMillis();
} catch (ParseException e) {
throw new IOException(e);
}
}
}
}
3 years ago
public String getNameOrFallback(String fallback) {
return this.name == null ||
this.name.equals(this.url) ?
fallback : this.name;
}
private static class SpecialData {
SpecialData(long time, int stars) {
this.time = time; this.stars = stars;
}
final long time;
final int stars;
@NonNull
@Override
public String toString() {
return "SpecialData{" +
"time=" + time +
", stars=" + stars +
'}';
}
}
3 years ago
}