more kotlin

Signed-off-by: androidacy-user <opensource@androidacy.com>
pull/27/head
androidacy-user 2 years ago
parent 8a54228509
commit bca8507ba5

@ -1,175 +1,173 @@
package com.fox2code.mmm; package com.fox2code.mmm
import com.fox2code.mmm.utils.io.Files; import com.fox2code.mmm.utils.io.Files.Companion.write
import com.fox2code.mmm.utils.io.net.Http; import com.fox2code.mmm.utils.io.net.Http.Companion.doHttpGet
import org.json.JSONObject
import org.json.JSONObject; import timber.log.Timber
import java.io.BufferedReader
import java.io.BufferedReader; import java.io.File
import java.io.File; import java.io.FileInputStream
import java.io.FileInputStream; import java.io.IOException
import java.io.IOException; import java.io.InputStream
import java.io.InputStream; import java.io.InputStreamReader
import java.io.InputStreamReader; import java.nio.charset.StandardCharsets
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import timber.log.Timber;
// See https://docs.github.com/en/rest/reference/repos#releases // See https://docs.github.com/en/rest/reference/repos#releases
public class AppUpdateManager { @Suppress("unused")
public static final int FLAG_COMPAT_LOW_QUALITY = 0x0001; class AppUpdateManager private constructor() {
public static final int FLAG_COMPAT_NO_EXT = 0x0002; private val compatDataId = HashMap<String, Int>()
public static final int FLAG_COMPAT_MAGISK_CMD = 0x0004; private val updateLock = Any()
public static final int FLAG_COMPAT_NEED_32BIT = 0x0008; private val compatFile: File = File(MainApplication.getINSTANCE().filesDir, "compat.txt")
public static final int FLAG_COMPAT_MALWARE = 0x0010; private var latestRelease: String?
public static final int FLAG_COMPAT_NO_ANSI = 0x0020; private var lastChecked: Long
public static final int FLAG_COMPAT_FORCE_ANSI = 0x0040;
public static final int FLAG_COMPAT_FORCE_HIDE = 0x0080;
public static final int FLAG_COMPAT_MMT_REBORN = 0x0100;
public static final int FLAG_COMPAT_ZIP_WRAPPER = 0x0200;
public static final String RELEASES_API_URL = "https://api.github.com/repos/Androidacy/MagiskModuleManager/releases/latest";
private static final AppUpdateManager INSTANCE = new AppUpdateManager();
private final HashMap<String, Integer> compatDataId = new HashMap<>();
private final Object updateLock = new Object();
private final File compatFile;
private String latestRelease;
private long lastChecked;
private AppUpdateManager() { init {
this.compatFile = new File(MainApplication.getINSTANCE().getFilesDir(), "compat.txt"); latestRelease = MainApplication.getBootSharedPreferences()
this.latestRelease = MainApplication.getBootSharedPreferences().getString("updater_latest_release", BuildConfig.VERSION_NAME); .getString("updater_latest_release", BuildConfig.VERSION_NAME)
this.lastChecked = 0; lastChecked = 0
if (this.compatFile.isFile()) { if (compatFile.isFile) {
try { try {
this.parseCompatibilityFlags(new FileInputStream(this.compatFile)); parseCompatibilityFlags(FileInputStream(compatFile))
} catch ( } catch (ignored: IOException) {
IOException ignored) {
}
}
}
public static AppUpdateManager getAppUpdateManager() {
return INSTANCE;
} }
public static int getFlagsForModule(String moduleId) {
return INSTANCE.getCompatibilityFlags(moduleId);
} }
public static boolean shouldForceHide(String repoId) {
if (BuildConfig.DEBUG || repoId.startsWith("repo_") || repoId.equals("magisk_alt_repo"))
return false;
return !repoId.startsWith("repo_") && (INSTANCE.getCompatibilityFlags(repoId) & FLAG_COMPAT_FORCE_HIDE) != 0;
} }
// Return true if should show a notification // Return true if should show a notification
public boolean checkUpdate(boolean force) { fun checkUpdate(force: Boolean): Boolean {
if (!BuildConfig.ENABLE_AUTO_UPDATER) if (!BuildConfig.ENABLE_AUTO_UPDATER) return false
return false; if (!force && peekShouldUpdate()) return true
if (!force && this.peekShouldUpdate()) val lastChecked = lastChecked
return true; if (lastChecked != 0L && // Avoid spam calls by putting a 60 seconds timer
long lastChecked = this.lastChecked; lastChecked < System.currentTimeMillis() - 60000L
if (lastChecked != 0 && ) return force && peekShouldUpdate()
// Avoid spam calls by putting a 60 seconds timer synchronized(updateLock) {
lastChecked < System.currentTimeMillis() - 60000L) if (lastChecked != this.lastChecked) return peekShouldUpdate()
return force && this.peekShouldUpdate();
synchronized (this.updateLock) {
if (lastChecked != this.lastChecked)
return this.peekShouldUpdate();
try { try {
JSONObject release = new JSONObject(new String(Http.doHttpGet(RELEASES_API_URL, false), StandardCharsets.UTF_8)); val release =
String latestRelease = null; JSONObject(String(doHttpGet(RELEASES_API_URL, false), StandardCharsets.UTF_8))
boolean preRelease = false; var latestRelease: String? = null
var preRelease = false
// get latest_release from tag_name translated to int // get latest_release from tag_name translated to int
if (release.has("tag_name")) { if (release.has("tag_name")) {
latestRelease = release.getString("tag_name"); latestRelease = release.getString("tag_name")
preRelease = release.getBoolean("prerelease"); preRelease = release.getBoolean("prerelease")
} }
Timber.d("Latest release: %s, isPreRelease: %s", latestRelease, preRelease); Timber.d("Latest release: %s, isPreRelease: %s", latestRelease, preRelease)
if (latestRelease == null) if (latestRelease == null) return false
return false;
if (preRelease) { if (preRelease) {
this.latestRelease = "99999999"; // prevent updating to pre-release this.latestRelease = "99999999" // prevent updating to pre-release
return false; return false
} }
this.latestRelease = latestRelease; this.latestRelease = latestRelease
this.lastChecked = System.currentTimeMillis(); this.lastChecked = System.currentTimeMillis()
} catch ( } catch (ioe: Exception) {
Exception ioe) { Timber.e(ioe)
Timber.e(ioe);
} }
} }
return this.peekShouldUpdate(); return peekShouldUpdate()
} }
public void checkUpdateCompat() { fun checkUpdateCompat() {
compatDataId.clear(); compatDataId.clear()
try { try {
Files.write(compatFile, new byte[0]); write(compatFile, ByteArray(0))
} catch ( } catch (e: IOException) {
IOException e) { Timber.e(e)
Timber.e(e);
} }
// There once lived an implementation that used a GitHub API to get the compatibility flags. It was removed because it was too slow and the API was rate limited. // There once lived an implementation that used a GitHub API to get the compatibility flags. It was removed because it was too slow and the API was rate limited.
Timber.w("Remote compatibility data flags are not implemented."); Timber.w("Remote compatibility data flags are not implemented.")
} }
public boolean peekShouldUpdate() { fun peekShouldUpdate(): Boolean {
if (!BuildConfig.ENABLE_AUTO_UPDATER || BuildConfig.DEBUG) if (!BuildConfig.ENABLE_AUTO_UPDATER || BuildConfig.DEBUG) return false
return false;
// Convert both BuildConfig.VERSION_NAME and latestRelease to int // Convert both BuildConfig.VERSION_NAME and latestRelease to int
int currentVersion = 0, latestVersion = 0; var currentVersion = 0
var latestVersion = 0
try { try {
currentVersion = Integer.parseInt(BuildConfig.VERSION_NAME.replaceAll("\\D", "")); currentVersion = BuildConfig.VERSION_NAME.replace("\\D".toRegex(), "").toInt()
latestVersion = Integer.parseInt(this.latestRelease.replace("v", "").replaceAll("\\D", "")); latestVersion = latestRelease!!.replace("v", "").replace("\\D".toRegex(), "").toInt()
} catch ( } catch (ignored: NumberFormatException) {
NumberFormatException ignored) {
} }
return currentVersion < latestVersion; return currentVersion < latestVersion
}
fun peekHasUpdate(): Boolean {
return if (!BuildConfig.ENABLE_AUTO_UPDATER || BuildConfig.DEBUG) false else peekShouldUpdate()
} }
public boolean peekHasUpdate() { @Throws(IOException::class)
if (!BuildConfig.ENABLE_AUTO_UPDATER || BuildConfig.DEBUG) private fun parseCompatibilityFlags(inputStream: InputStream) {
return false; compatDataId.clear()
return this.peekShouldUpdate(); val bufferedReader = BufferedReader(InputStreamReader(inputStream, StandardCharsets.UTF_8))
var line: String
while (bufferedReader.readLine().also { line = it } != null) {
line = line.trim { it <= ' ' }
if (line.isEmpty() || line.startsWith("#")) continue
val i = line.indexOf('/')
if (i == -1) continue
var value = 0
for (arg in line.substring(i + 1).split(",".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()) {
when (arg) {
"lowQuality" -> value = value or FLAG_COMPAT_LOW_QUALITY
"noExt" -> value = value or FLAG_COMPAT_NO_EXT
"magiskCmd" -> value = value or FLAG_COMPAT_MAGISK_CMD
"need32bit" -> value = value or FLAG_COMPAT_NEED_32BIT
"malware" -> value = value or FLAG_COMPAT_MALWARE
"noANSI" -> value = value or FLAG_COMPAT_NO_ANSI
"forceANSI" -> value = value or FLAG_COMPAT_FORCE_ANSI
"forceHide" -> value = value or FLAG_COMPAT_FORCE_HIDE
"mmtReborn" -> value = value or FLAG_COMPAT_MMT_REBORN
"wrapper" -> value = value or FLAG_COMPAT_ZIP_WRAPPER
else -> {
run {}
value = value or FLAG_COMPAT_LOW_QUALITY
value = value or FLAG_COMPAT_NO_EXT
value = value or FLAG_COMPAT_MAGISK_CMD
value = value or FLAG_COMPAT_NEED_32BIT
value = value or FLAG_COMPAT_MALWARE
value = value or FLAG_COMPAT_NO_ANSI
value = value or FLAG_COMPAT_FORCE_ANSI
value = value or FLAG_COMPAT_FORCE_HIDE
value = value or FLAG_COMPAT_MMT_REBORN
value = value or FLAG_COMPAT_ZIP_WRAPPER
}
}
}
compatDataId[line.substring(0, i)] = value
}
bufferedReader.close()
} }
private void parseCompatibilityFlags(InputStream inputStream) throws IOException { fun getCompatibilityFlags(moduleId: String): Int {
compatDataId.clear(); val compatFlags = compatDataId[moduleId]
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); return compatFlags ?: 0
String line;
while ((line = bufferedReader.readLine()) != null) {
line = line.trim();
if (line.isEmpty() || line.startsWith("#"))
continue;
int i = line.indexOf('/');
if (i == -1)
continue;
int value = 0;
for (String arg : line.substring(i + 1).split(",")) {
switch (arg) {
default -> {
}
case "lowQuality" -> value |= FLAG_COMPAT_LOW_QUALITY;
case "noExt" -> value |= FLAG_COMPAT_NO_EXT;
case "magiskCmd" -> value |= FLAG_COMPAT_MAGISK_CMD;
case "need32bit" -> value |= FLAG_COMPAT_NEED_32BIT;
case "malware" -> value |= FLAG_COMPAT_MALWARE;
case "noANSI" -> value |= FLAG_COMPAT_NO_ANSI;
case "forceANSI" -> value |= FLAG_COMPAT_FORCE_ANSI;
case "forceHide" -> value |= FLAG_COMPAT_FORCE_HIDE;
case "mmtReborn" -> value |= FLAG_COMPAT_MMT_REBORN;
case "wrapper" -> value |= FLAG_COMPAT_ZIP_WRAPPER;
}
}
compatDataId.put(line.substring(0, i), value);
}
bufferedReader.close();
} }
public int getCompatibilityFlags(String moduleId) { companion object {
Integer compatFlags = compatDataId.get(moduleId); const val FLAG_COMPAT_LOW_QUALITY = 0x0001
return compatFlags == null ? 0 : compatFlags; const val FLAG_COMPAT_NO_EXT = 0x0002
const val FLAG_COMPAT_MAGISK_CMD = 0x0004
const val FLAG_COMPAT_NEED_32BIT = 0x0008
const val FLAG_COMPAT_MALWARE = 0x0010
const val FLAG_COMPAT_NO_ANSI = 0x0020
const val FLAG_COMPAT_FORCE_ANSI = 0x0040
const val FLAG_COMPAT_FORCE_HIDE = 0x0080
const val FLAG_COMPAT_MMT_REBORN = 0x0100
const val FLAG_COMPAT_ZIP_WRAPPER = 0x0200
const val RELEASES_API_URL =
"https://api.github.com/repos/Androidacy/MagiskModuleManager/releases/latest"
val appUpdateManager = AppUpdateManager()
fun getFlagsForModule(moduleId: String): Int {
return appUpdateManager.getCompatibilityFlags(moduleId)
}
@JvmStatic
fun shouldForceHide(repoId: String): Boolean {
return if (BuildConfig.DEBUG || repoId.startsWith("repo_") || repoId == "magisk_alt_repo") false else !repoId.startsWith(
"repo_"
) && appUpdateManager.getCompatibilityFlags(repoId) and FLAG_COMPAT_FORCE_HIDE != 0
}
} }
} }

@ -145,7 +145,7 @@ enum class NotificationType constructor(
false false
) { ) {
override fun shouldRemove(): Boolean { override fun shouldRemove(): Boolean {
return !AppUpdateManager.getAppUpdateManager().peekShouldUpdate() return !AppUpdateManager.appUpdateManager.peekShouldUpdate()
} }
}, },
@JvmStatic @JvmStatic

@ -177,7 +177,7 @@ class UpdateActivity : FoxActivity() {
progressIndicator.isIndeterminate = true progressIndicator.isIndeterminate = true
} }
// check for update // check for update
val shouldUpdate = AppUpdateManager.getAppUpdateManager().peekShouldUpdate() val shouldUpdate = AppUpdateManager.appUpdateManager.peekShouldUpdate()
// if shouldUpdate is true, then we have an update // if shouldUpdate is true, then we have an update
if (shouldUpdate) { if (shouldUpdate) {
runOnUiThread { runOnUiThread {

@ -276,7 +276,7 @@ class BackgroundUpdateChecker(context: Context, workerParams: WorkerParameters)
.getBoolean("pref_background_update_check_app", false) .getBoolean("pref_background_update_check_app", false)
) { ) {
try { try {
val shouldUpdate = AppUpdateManager.getAppUpdateManager().checkUpdate(true) val shouldUpdate = AppUpdateManager.appUpdateManager.checkUpdate(true)
if (shouldUpdate) { if (shouldUpdate) {
Timber.d("Found app update") Timber.d("Found app update")
postNotificationForAppUpdate(context) postNotificationForAppUpdate(context)

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