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.
365 lines
18 KiB
Java
365 lines
18 KiB
Java
package com.fox2code.mmm.repo;
|
|
|
|
import com.fox2code.mmm.MainApplication;
|
|
import com.fox2code.mmm.utils.io.net.Http;
|
|
import com.fox2code.mmm.utils.realm.ModuleListCache;
|
|
import com.fox2code.mmm.utils.realm.ReposList;
|
|
|
|
import org.json.JSONArray;
|
|
import org.json.JSONObject;
|
|
|
|
import java.io.File;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
import io.realm.Realm;
|
|
import io.realm.RealmConfiguration;
|
|
import io.realm.RealmResults;
|
|
import timber.log.Timber;
|
|
|
|
public class RepoUpdater {
|
|
public final RepoData repoData;
|
|
public byte[] indexRaw;
|
|
private List<RepoModule> toUpdate;
|
|
private Collection<RepoModule> toApply;
|
|
|
|
public RepoUpdater(RepoData repoData) {
|
|
this.repoData = repoData;
|
|
}
|
|
|
|
public int fetchIndex() {
|
|
if (!RepoManager.getINSTANCE().hasConnectivity()) {
|
|
this.indexRaw = null;
|
|
this.toUpdate = Collections.emptyList();
|
|
this.toApply = Collections.emptySet();
|
|
return 0;
|
|
}
|
|
if (!this.repoData.isEnabled()) {
|
|
this.indexRaw = null;
|
|
this.toUpdate = Collections.emptyList();
|
|
this.toApply = Collections.emptySet();
|
|
return 0;
|
|
}
|
|
// if we shouldn't update, get the values from the ModuleListCache realm
|
|
if (!this.repoData.shouldUpdate() && Objects.equals(this.repoData.id, "androidacy_repo")) { // for now, only enable cache reading for androidacy repo, until we handle storing module prop file values in cache
|
|
Timber.d("Fetching index from cache for %s", this.repoData.id);
|
|
File cacheRoot = MainApplication.getINSTANCE().getDataDirWithPath("realms/repos/" + this.repoData.id);
|
|
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ModuleListCache.realm").encryptionKey(MainApplication.getINSTANCE().getKey()).schemaVersion(1).deleteRealmIfMigrationNeeded().allowWritesOnUiThread(true).allowQueriesOnUiThread(true).directory(cacheRoot).build();
|
|
Realm realm = Realm.getInstance(realmConfiguration);
|
|
RealmResults<ModuleListCache> results = realm.where(ModuleListCache.class).equalTo("repoId", this.repoData.id).findAll();
|
|
// repos-list realm
|
|
RealmConfiguration realmConfiguration2 = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getKey()).allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build();
|
|
Realm realm2 = Realm.getInstance(realmConfiguration2);
|
|
this.toUpdate = Collections.emptyList();
|
|
this.toApply = new HashSet<>();
|
|
for (ModuleListCache moduleListCache : results) {
|
|
this.toApply.add(new RepoModule(this.repoData, moduleListCache.getCodename(), moduleListCache.getName(), moduleListCache.getDescription(), moduleListCache.getAuthor(), moduleListCache.getDonate(), moduleListCache.getConfig(), moduleListCache.getSupport(), moduleListCache.getVersion(), moduleListCache.getVersionCode()));
|
|
}
|
|
Timber.d("Fetched %d modules from cache for %s, from %s records", this.toApply.size(), this.repoData.id, results.size());
|
|
// apply the toApply list to the toUpdate list
|
|
try {
|
|
JSONObject jsonObject = new JSONObject();
|
|
jsonObject.put("modules", new JSONArray(results.asJSON()));
|
|
this.toUpdate = this.repoData.populate(jsonObject);
|
|
} catch (Exception e) {
|
|
Timber.e(e);
|
|
}
|
|
// close realm
|
|
realm.close();
|
|
realm2.close();
|
|
// Since we reuse instances this should work
|
|
this.toApply = new HashSet<>(this.repoData.moduleHashMap.values());
|
|
this.toApply.removeAll(this.toUpdate);
|
|
// Return repo to update
|
|
return this.toUpdate.size();
|
|
}
|
|
try {
|
|
if (!this.repoData.prepare()) {
|
|
this.indexRaw = null;
|
|
this.toUpdate = Collections.emptyList();
|
|
this.toApply = this.repoData.moduleHashMap.values();
|
|
return 0;
|
|
}
|
|
this.indexRaw = Http.doHttpGet(this.repoData.getUrl(), false);
|
|
this.toUpdate = this.repoData.populate(new JSONObject(new String(this.indexRaw, StandardCharsets.UTF_8)));
|
|
// Since we reuse instances this should work
|
|
this.toApply = new HashSet<>(this.repoData.moduleHashMap.values());
|
|
this.toApply.removeAll(this.toUpdate);
|
|
// Return repo to update
|
|
return this.toUpdate.size();
|
|
} catch (
|
|
Exception e) {
|
|
Timber.e(e);
|
|
this.indexRaw = null;
|
|
this.toUpdate = Collections.emptyList();
|
|
this.toApply = Collections.emptySet();
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public List<RepoModule> toUpdate() {
|
|
return this.toUpdate;
|
|
}
|
|
|
|
public Collection<RepoModule> toApply() {
|
|
return this.toApply;
|
|
}
|
|
|
|
public boolean finish() {
|
|
var success = new AtomicBoolean(false);
|
|
// If repo is not enabled we don't need to do anything, just return true
|
|
if (!this.repoData.isEnabled()) {
|
|
return true;
|
|
}
|
|
if (this.indexRaw != null) {
|
|
try {
|
|
// iterate over modules, using this.supportedProperties as a template to attempt to get each property from the module. everything that is not null is added to the module
|
|
// use realm to insert to
|
|
// props avail:
|
|
File cacheRoot = MainApplication.getINSTANCE().getDataDirWithPath("realms/repos/" + this.repoData.id);
|
|
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ModuleListCache.realm").encryptionKey(MainApplication.getINSTANCE().getKey()).schemaVersion(1).deleteRealmIfMigrationNeeded().allowWritesOnUiThread(true).allowQueriesOnUiThread(true).directory(cacheRoot).build();
|
|
// array with module info default values
|
|
// supported properties for a module
|
|
//id=<string>
|
|
//name=<string>
|
|
//version=<string>
|
|
//versionCode=<int>
|
|
//author=<string>
|
|
//description=<string>
|
|
//minApi=<int>
|
|
//maxApi=<int>
|
|
//minMagisk=<int>
|
|
//needRamdisk=<boolean>
|
|
//support=<url>
|
|
//donate=<url>
|
|
//config=<package>
|
|
//changeBoot=<boolean>
|
|
//mmtReborn=<boolean>
|
|
// extra properties only useful for the database
|
|
//repoId=<string>
|
|
//installed=<boolean>
|
|
//installedVersionCode=<int> (only if installed)
|
|
//
|
|
// all except first six can be null
|
|
// this.indexRaw is the raw index file (json)
|
|
JSONObject modules = new JSONObject(new String(this.indexRaw, StandardCharsets.UTF_8));
|
|
JSONArray modulesArray;
|
|
// androidacy repo uses "data" key, others should use "modules" key. Both are JSONArrays
|
|
if (this.repoData.getName().equals("Androidacy Modules Repo")) {
|
|
// get modules from "data" key. This is a JSONArray so we need to convert it to a JSONObject
|
|
modulesArray = modules.getJSONArray("data");
|
|
} else {
|
|
// get modules from "modules" key. This is a JSONArray so we need to convert it to a JSONObject
|
|
modulesArray = modules.getJSONArray("modules");
|
|
}
|
|
Realm realm = Realm.getInstance(realmConfiguration);
|
|
// drop old data
|
|
if (realm.isInTransaction()) {
|
|
realm.commitTransaction();
|
|
}
|
|
realm.beginTransaction();
|
|
realm.where(ModuleListCache.class).equalTo("repoId", this.repoData.id).findAll().deleteAllFromRealm();
|
|
realm.commitTransaction();
|
|
// iterate over modules. pls don't hate me for this, its ugly but it works
|
|
for (int n = 0; n < modulesArray.length(); n++) {
|
|
// get module
|
|
JSONObject module = modulesArray.getJSONObject(n);
|
|
try {
|
|
// get module id
|
|
// if codename is present, prefer that over id
|
|
String id;
|
|
if (module.has("codename") && !module.getString("codename").equals("")) {
|
|
id = module.getString("codename");
|
|
} else {
|
|
id = module.getString("id");
|
|
}
|
|
// get module name
|
|
String name = module.getString("name");
|
|
// get module version
|
|
String version = module.getString("version");
|
|
// get module version code
|
|
int versionCode = module.getInt("versionCode");
|
|
// get module author
|
|
String author = module.getString("author");
|
|
// get module description
|
|
String description = module.getString("description");
|
|
// get module min api
|
|
String minApi;
|
|
if (module.has("minApi") && !module.getString("minApi").equals("")) {
|
|
minApi = module.getString("minApi");
|
|
} else {
|
|
minApi = "0";
|
|
}
|
|
// coerce min api to int
|
|
int minApiInt = Integer.parseInt(minApi);
|
|
// get module max api and set to 0 if it's "" or null
|
|
String maxApi;
|
|
if (module.has("maxApi") && !module.getString("maxApi").equals("")) {
|
|
maxApi = module.getString("maxApi");
|
|
} else {
|
|
maxApi = "0";
|
|
}
|
|
// coerce max api to int
|
|
int maxApiInt = Integer.parseInt(maxApi);
|
|
// get module min magisk
|
|
String minMagisk;
|
|
if (module.has("minMagisk") && !module.getString("minMagisk").equals("")) {
|
|
minMagisk = module.getString("minMagisk");
|
|
} else {
|
|
minMagisk = "0";
|
|
}
|
|
// coerce min magisk to int
|
|
int minMagiskInt = Integer.parseInt(minMagisk);
|
|
// get module need ramdisk
|
|
boolean needRamdisk;
|
|
if (module.has("needRamdisk")) {
|
|
needRamdisk = module.getBoolean("needRamdisk");
|
|
} else {
|
|
needRamdisk = false;
|
|
}
|
|
// get module support
|
|
String support;
|
|
if (module.has("support")) {
|
|
support = module.getString("support");
|
|
} else {
|
|
support = "";
|
|
}
|
|
// get module donate
|
|
String donate;
|
|
if (module.has("donate")) {
|
|
donate = module.getString("donate");
|
|
} else {
|
|
donate = "";
|
|
}
|
|
// get module config
|
|
String config;
|
|
if (module.has("config")) {
|
|
config = module.getString("config");
|
|
} else {
|
|
config = "";
|
|
}
|
|
// get module change boot
|
|
boolean changeBoot;
|
|
if (module.has("changeBoot")) {
|
|
changeBoot = module.getBoolean("changeBoot");
|
|
} else {
|
|
changeBoot = false;
|
|
}
|
|
// get module mmt reborn
|
|
boolean mmtReborn;
|
|
if (module.has("mmtReborn")) {
|
|
mmtReborn = module.getBoolean("mmtReborn");
|
|
} else {
|
|
mmtReborn = false;
|
|
}
|
|
// try to get updated_at or lastUpdate value for lastUpdate
|
|
int lastUpdate;
|
|
if (module.has("updated_at")) {
|
|
lastUpdate = module.getInt("updated_at");
|
|
} else if (module.has("lastUpdate")) {
|
|
lastUpdate = module.getInt("lastUpdate");
|
|
} else {
|
|
lastUpdate = 0;
|
|
}
|
|
// now downloads or stars
|
|
int downloads;
|
|
if (module.has("downloads")) {
|
|
downloads = module.getInt("downloads");
|
|
} else if (module.has("stars")) {
|
|
downloads = module.getInt("stars");
|
|
} else {
|
|
downloads = 0;
|
|
}
|
|
// get module repo id
|
|
String repoId = this.repoData.id;
|
|
// get module installed
|
|
boolean installed = false;
|
|
// get module installed version code
|
|
int installedVersionCode = 0;
|
|
// get safe property. for now, only supported by androidacy repo and they use "vt_status" key
|
|
boolean safe = false;
|
|
if (this.repoData.getName().equals("Androidacy Modules Repo")) {
|
|
if (module.has("vt_status")) {
|
|
if (module.getString("vt_status").equals("Clean")) {
|
|
safe = true;
|
|
}
|
|
}
|
|
}
|
|
// insert module to realm
|
|
// first create a collection of all the properties
|
|
// then insert to realm
|
|
// then commit
|
|
// then close
|
|
if (realm.isInTransaction()) {
|
|
realm.cancelTransaction();
|
|
}
|
|
// create a realm object and insert or update it
|
|
// add everything to the realm object
|
|
if (realm.isInTransaction()) {
|
|
realm.commitTransaction();
|
|
}
|
|
realm.beginTransaction();
|
|
ModuleListCache moduleListCache = realm.createObject(ModuleListCache.class, id);
|
|
moduleListCache.setName(name);
|
|
moduleListCache.setVersion(version);
|
|
moduleListCache.setVersionCode(versionCode);
|
|
moduleListCache.setAuthor(author);
|
|
moduleListCache.setDescription(description);
|
|
moduleListCache.setMinApi(minApiInt);
|
|
moduleListCache.setMaxApi(maxApiInt);
|
|
moduleListCache.setMinMagisk(minMagiskInt);
|
|
moduleListCache.setNeedRamdisk(needRamdisk);
|
|
moduleListCache.setSupport(support);
|
|
moduleListCache.setDonate(donate);
|
|
moduleListCache.setConfig(config);
|
|
moduleListCache.setChangeBoot(changeBoot);
|
|
moduleListCache.setMmtReborn(mmtReborn);
|
|
moduleListCache.setRepoId(repoId);
|
|
moduleListCache.setInstalled(installed);
|
|
moduleListCache.setInstalledVersionCode(installedVersionCode);
|
|
moduleListCache.setSafe(safe);
|
|
moduleListCache.setLastUpdate(lastUpdate);
|
|
moduleListCache.setStats(downloads);
|
|
realm.copyToRealmOrUpdate(moduleListCache);
|
|
realm.commitTransaction();
|
|
} catch (
|
|
Exception ignored) {
|
|
}
|
|
}
|
|
realm.close();
|
|
} catch (
|
|
Exception ignored) {
|
|
}
|
|
this.indexRaw = null;
|
|
RealmConfiguration realmConfiguration2 = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getKey()).allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build();
|
|
Realm realm2 = Realm.getInstance(realmConfiguration2);
|
|
if (realm2.isInTransaction()) {
|
|
realm2.cancelTransaction();
|
|
}
|
|
// set lastUpdate
|
|
realm2.executeTransaction(r -> {
|
|
ReposList repoListCache = r.where(ReposList.class).equalTo("id", this.repoData.id).findFirst();
|
|
if (repoListCache != null) {
|
|
success.set(true);
|
|
// get unix timestamp of current time
|
|
int currentTime = (int) (System.currentTimeMillis() / 1000);
|
|
Timber.d("Updating lastUpdate for repo %s to %s which is %s seconds ago", this.repoData.id, currentTime, (currentTime - repoListCache.getLastUpdate()));
|
|
repoListCache.setLastUpdate(currentTime);
|
|
} else {
|
|
Timber.w("Failed to update lastUpdate for repo %s", this.repoData.id);
|
|
}
|
|
});
|
|
realm2.close();
|
|
} else {
|
|
success.set(true); // assume we're reading from cache. this may be unsafe but it's better than nothing
|
|
}
|
|
return success.get();
|
|
}
|
|
|
|
}
|