diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/MagiskModule.kt b/app/src/main/java/com/topjohnwu/magisk/model/entity/MagiskModule.kt index 7618f9cff..5846388e9 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/entity/MagiskModule.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/entity/MagiskModule.kt @@ -1,86 +1,111 @@ package com.topjohnwu.magisk.model.entity -import android.os.Parcelable -import androidx.annotation.AnyThread -import androidx.annotation.NonNull import androidx.annotation.WorkerThread -import androidx.room.Entity -import androidx.room.PrimaryKey import com.topjohnwu.magisk.Const -import com.topjohnwu.magisk.data.database.base.su -import io.reactivex.Single -import kotlinx.android.parcel.Parcelize -import okhttp3.ResponseBody -import java.io.File +import com.topjohnwu.superuser.Shell +import com.topjohnwu.superuser.io.SuFile -interface MagiskModule : Parcelable { - val id: String - val name: String - val author: String - val version: String - val versionCode: String - val description: String +abstract class BaseModule : Comparable { + abstract var id: String + protected set + abstract var name: String + protected set + abstract var author: String + protected set + abstract var version: String + protected set + abstract var versionCode: Int + protected set + abstract var description: String + protected set + + @Throws(NumberFormatException::class) + protected fun parseProps(props: List) { + for (line in props) { + val prop = line.split("=".toRegex(), 2).map { it.trim() } + if (prop.size != 2) + continue + + val key = prop[0] + val value = prop[1] + if (key.isEmpty() || key[0] == '#') + continue + + when (key) { + "id" -> id = value + "name" -> name = value + "version" -> version = value + "versionCode" -> versionCode = value.toInt() + "author" -> author = value + "description" -> description = value + } + } + } + + override operator fun compareTo(other: BaseModule) = name.compareTo(other.name, true) } -@Entity(tableName = "repos") -@Parcelize -data class Repository( - @PrimaryKey @NonNull - override val id: String, - override val name: String, - override val author: String, - override val version: String, - override val versionCode: String, - override val description: String, - val lastUpdate: Long -) : MagiskModule +class Module(path: String) : BaseModule() { + override var id: String = "" + override var name: String = "" + override var author: String = "" + override var version: String = "" + override var versionCode: Int = -1 + override var description: String = "" -@Parcelize -data class Module( - override val id: String, - override val name: String, - override val author: String, - override val version: String, - override val versionCode: String, - override val description: String, - val path: String -) : MagiskModule + private val removeFile: SuFile = SuFile(path, "remove") + private val disableFile: SuFile = SuFile(path, "disable") + private val updateFile: SuFile = SuFile(path, "update") -@AnyThread -fun File.toModule(): Single { - val path = "${Const.MAGISK_PATH}/$name" - return "dos2unix < $path/module.prop".su() - .map { it.first().toModule(path) } + val updated: Boolean = updateFile.exists() + + var enable: Boolean = !disableFile.exists() + set(enable) { + field = if (enable) { + disableFile.delete() + } else { + !disableFile.createNewFile() + } + } + + var remove: Boolean = removeFile.exists() + set(remove) { + field = if (remove) { + removeFile.createNewFile() + } else { + !removeFile.delete() + } + } + + init { + runCatching { + parseProps(Shell.su("dos2unix < $path/module.prop").exec().out) + } + + if (id.isEmpty()) { + val sep = path.lastIndexOf('/') + id = path.substring(sep + 1) + } + + if (name.isEmpty()) { + name = id; + } + } + + companion object { + + @WorkerThread + fun loadModules(): List { + val moduleList = mutableListOf() + val path = SuFile(Const.MAGISK_PATH) + val modules = + path.listFiles { _, name -> name != "lost+found" && name != ".core" }.orEmpty() + for (file in modules) { + if (file.isFile) continue + val module = Module(Const.MAGISK_PATH + "/" + file.name) + moduleList.add(module) + } + return moduleList + } + } } - -fun Map.toModule(path: String): Module { - return Module( - id = get("id").orEmpty(), - name = get("name").orEmpty(), - author = get("author").orEmpty(), - version = get("version").orEmpty(), - versionCode = get("versionCode").orEmpty(), - description = get("description").orEmpty(), - path = path - ) -} - -@WorkerThread -fun ResponseBody.toRepository(lastUpdate: Long) = string() - .split(Regex("\\n")) - .map { it.split("=", limit = 2) } - .filter { it.size == 2 } - .map { Pair(it[0], it[1]) } - .toMap() - .toRepository(lastUpdate) - -@AnyThread -fun Map.toRepository(lastUpdate: Long) = Repository( - id = get("id").orEmpty(), - name = get("name").orEmpty(), - author = get("author").orEmpty(), - version = get("version").orEmpty(), - versionCode = get("versionCode").orEmpty(), - description = get("description").orEmpty(), - lastUpdate = lastUpdate -) \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/BaseModule.java b/app/src/main/java/com/topjohnwu/magisk/model/entity/OldBaseModule.java similarity index 87% rename from app/src/main/java/com/topjohnwu/magisk/model/entity/BaseModule.java rename to app/src/main/java/com/topjohnwu/magisk/model/entity/OldBaseModule.java index b9bb1ae9a..a8556e65c 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/entity/BaseModule.java +++ b/app/src/main/java/com/topjohnwu/magisk/model/entity/OldBaseModule.java @@ -9,16 +9,16 @@ import androidx.annotation.NonNull; import java.util.List; -public abstract class BaseModule implements Comparable, Parcelable { +public abstract class OldBaseModule implements Comparable, Parcelable { private String mId, mName, mVersion, mAuthor, mDescription; private int mVersionCode = -1; - protected BaseModule() { + protected OldBaseModule() { mId = mName = mVersion = mAuthor = mDescription = ""; } - protected BaseModule(Cursor c) { + protected OldBaseModule(Cursor c) { mId = nonNull(c.getString(c.getColumnIndex("id"))); mName = nonNull(c.getString(c.getColumnIndex("name"))); mVersion = nonNull(c.getString(c.getColumnIndex("version"))); @@ -27,7 +27,7 @@ public abstract class BaseModule implements Comparable, Parcelable { mDescription = nonNull(c.getString(c.getColumnIndex("description"))); } - protected BaseModule(Parcel p) { + protected OldBaseModule(Parcel p) { mId = p.readString(); mName = p.readString(); mVersion = p.readString(); @@ -36,17 +36,8 @@ public abstract class BaseModule implements Comparable, Parcelable { mVersionCode = p.readInt(); } - protected BaseModule(MagiskModule m) { - mId = m.getId(); - mName = m.getName(); - mVersion = m.getVersion(); - mAuthor = m.getAuthor(); - mDescription = m.getDescription(); - mVersionCode = Integer.parseInt(m.getVersionCode()); - } - @Override - public int compareTo(@NonNull BaseModule module) { + public int compareTo(@NonNull OldBaseModule module) { return getName().toLowerCase().compareTo(module.getName().toLowerCase()); } diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/OldModule.java b/app/src/main/java/com/topjohnwu/magisk/model/entity/OldModule.java deleted file mode 100644 index f4780c83b..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/model/entity/OldModule.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.topjohnwu.magisk.model.entity; - -import android.os.Parcel; -import android.os.Parcelable; - -import com.topjohnwu.superuser.Shell; -import com.topjohnwu.superuser.io.SuFile; - -public class OldModule extends BaseModule { - - public static final Parcelable.Creator CREATOR = new Creator() { - /* It won't be used at any place */ - @Override - public OldModule createFromParcel(Parcel source) { - return null; - } - - @Override - public OldModule[] newArray(int size) { - return null; - } - }; - private final SuFile mRemoveFile; - private final SuFile mDisableFile; - private final SuFile mUpdateFile; - private final boolean mUpdated; - private boolean mEnable; - private boolean mRemove; - - public OldModule(String path) { - - try { - parseProps(Shell.su("dos2unix < " + path + "/module.prop").exec().getOut()); - } catch (NumberFormatException ignored) { - } - - mRemoveFile = new SuFile(path, "remove"); - mDisableFile = new SuFile(path, "disable"); - mUpdateFile = new SuFile(path, "update"); - - if (getId().isEmpty()) { - int sep = path.lastIndexOf('/'); - setId(path.substring(sep + 1)); - } - - if (getName().isEmpty()) { - setName(getId()); - } - - mEnable = !mDisableFile.exists(); - mRemove = mRemoveFile.exists(); - mUpdated = mUpdateFile.exists(); - } - - public void createDisableFile() { - mEnable = !mDisableFile.createNewFile(); - } - - public void removeDisableFile() { - mEnable = mDisableFile.delete(); - } - - public boolean isEnabled() { - return mEnable; - } - - public void createRemoveFile() { - mRemove = mRemoveFile.createNewFile(); - } - - public void deleteRemoveFile() { - mRemove = !mRemoveFile.delete(); - } - - public boolean willBeRemoved() { - return mRemove; - } - - public boolean isUpdated() { - return mUpdated; - } - -} diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/Repo.java b/app/src/main/java/com/topjohnwu/magisk/model/entity/Repo.java index abd44c9e8..e16b0ef26 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/entity/Repo.java +++ b/app/src/main/java/com/topjohnwu/magisk/model/entity/Repo.java @@ -12,7 +12,7 @@ import com.topjohnwu.magisk.utils.XStringKt; import java.text.DateFormat; import java.util.Date; -public class Repo extends BaseModule { +public class Repo extends OldBaseModule { private Date mLastUpdate; @@ -30,11 +30,6 @@ public class Repo extends BaseModule { mLastUpdate = new Date(p.readLong()); } - public Repo(Repository repo) { - super(repo); - mLastUpdate = new Date(repo.getLastUpdate()); - } - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override @@ -107,7 +102,7 @@ public class Repo extends BaseModule { return XStringKt.legalFilename(getName() + "-" + getVersion() + ".zip"); } - public class IllegalRepoException extends Exception { + public static class IllegalRepoException extends Exception { IllegalRepoException(String message) { super(message); } diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/ModuleRvItem.kt b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/ModuleRvItem.kt index 8a3c36398..2b49c6672 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/ModuleRvItem.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/ModuleRvItem.kt @@ -6,44 +6,54 @@ import com.skoumal.teanity.databinding.ComparableRvItem import com.skoumal.teanity.extensions.addOnPropertyChangedCallback import com.skoumal.teanity.util.KObservableField import com.topjohnwu.magisk.R -import com.topjohnwu.magisk.model.entity.OldModule +import com.topjohnwu.magisk.model.entity.Module import com.topjohnwu.magisk.model.entity.Repo -import com.topjohnwu.magisk.model.entity.Repository import com.topjohnwu.magisk.utils.get import com.topjohnwu.magisk.utils.toggle -class ModuleRvItem(val item: OldModule) : ComparableRvItem() { +class ModuleRvItem(val item: Module) : ComparableRvItem() { override val layoutRes: Int = R.layout.item_module val lastActionNotice = KObservableField("") - val isChecked = KObservableField(item.isEnabled) - val isDeletable = KObservableField(item.willBeRemoved()) + val isChecked = KObservableField(item.enable) + val isDeletable = KObservableField(item.remove) init { isChecked.addOnPropertyChangedCallback { when (it) { - true -> item.removeDisableFile().notice(R.string.disable_file_removed) - false -> item.createDisableFile().notice(R.string.disable_file_created) + true -> { + item.enable = true + notice(R.string.disable_file_removed) + } + false -> { + item.enable = false + notice(R.string.disable_file_created) + } } } isDeletable.addOnPropertyChangedCallback { when (it) { - true -> item.createRemoveFile().notice(R.string.remove_file_created) - false -> item.deleteRemoveFile().notice(R.string.remove_file_deleted) + true -> { + item.remove = true + notice(R.string.remove_file_created) + } + false -> { + item.remove = false + notice(R.string.remove_file_deleted) + } } } when { - item.isUpdated -> notice(R.string.update_file_created) - item.willBeRemoved() -> notice(R.string.remove_file_created) + item.updated -> notice(R.string.update_file_created) + item.remove -> notice(R.string.remove_file_created) } } fun toggle() = isChecked.toggle() fun toggleDelete() = isDeletable.toggle() - @Suppress("unused") - private fun Any.notice(@StringRes info: Int) { + private fun notice(@StringRes info: Int) { lastActionNotice.value = get().getString(info) } @@ -57,8 +67,6 @@ class ModuleRvItem(val item: OldModule) : ComparableRvItem() { class RepoRvItem(val item: Repo) : ComparableRvItem() { - constructor(repo: Repository) : this(Repo(repo)) - override val layoutRes: Int = R.layout.item_repo override fun contentSameAs(other: RepoRvItem): Boolean = item.version == other.item.version diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt index 907ee89f6..74ba92c45 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt @@ -1,7 +1,6 @@ package com.topjohnwu.magisk.ui.module import android.content.res.Resources -import android.database.Cursor import com.skoumal.teanity.databinding.ComparableRvItem import com.skoumal.teanity.extensions.addOnPropertyChangedCallback import com.skoumal.teanity.extensions.doOnSuccessUi @@ -11,6 +10,7 @@ import com.skoumal.teanity.util.KObservableField import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.R import com.topjohnwu.magisk.data.database.RepoDatabaseHelper +import com.topjohnwu.magisk.model.entity.Module import com.topjohnwu.magisk.model.entity.Repo import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem import com.topjohnwu.magisk.model.entity.recycler.RepoRvItem @@ -20,7 +20,7 @@ import com.topjohnwu.magisk.model.events.OpenChangelogEvent import com.topjohnwu.magisk.model.events.OpenFilePickerEvent import com.topjohnwu.magisk.tasks.UpdateRepos import com.topjohnwu.magisk.ui.base.MagiskViewModel -import com.topjohnwu.magisk.utils.Utils +import com.topjohnwu.magisk.utils.toList import com.topjohnwu.magisk.utils.toSingle import com.topjohnwu.magisk.utils.update import io.reactivex.Single @@ -59,8 +59,7 @@ class ModuleViewModel( fun downloadPressed(item: RepoRvItem) = InstallModuleEvent(item.item).publish() fun refresh(force: Boolean) { - Single.fromCallable { Utils.loadModulesLeanback() } - .map { it.values.toList() } + Single.fromCallable { Module.loadModules() } .flattenAsFlowable { it } .map { ModuleRvItem(it) } .toList() @@ -110,12 +109,6 @@ class ModuleViewModel( groupedItems.getOrElse(MODULE_REMOTE) { listOf() }.withTitle(R.string.not_installed) } - private fun Cursor.toList(transformer: (Cursor) -> Result): List { - val out = mutableListOf() - while (moveToNext()) out.add(transformer(this)) - return out - } - companion object { protected const val MODULE_INSTALLED = 0 protected const val MODULE_REMOTE = 1 diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/Utils.kt b/app/src/main/java/com/topjohnwu/magisk/utils/Utils.kt index fa5cdb435..cec5d8c47 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/Utils.kt +++ b/app/src/main/java/com/topjohnwu/magisk/utils/Utils.kt @@ -10,16 +10,13 @@ import android.content.res.Resources import android.net.Uri import android.os.Environment import android.widget.Toast -import androidx.annotation.WorkerThread import androidx.work.* import com.topjohnwu.magisk.* import com.topjohnwu.magisk.R -import com.topjohnwu.magisk.model.entity.OldModule import com.topjohnwu.magisk.model.update.UpdateCheckService import com.topjohnwu.net.Networking import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.internal.UiThreadHandler -import com.topjohnwu.superuser.io.SuFile import java.io.File import java.util.* import java.util.concurrent.TimeUnit @@ -70,19 +67,6 @@ object Utils { return info.loadLabel(pm).toString() } - @WorkerThread - fun loadModulesLeanback(): Map { - val moduleMap = ValueSortedMap() - val path = SuFile(Const.MAGISK_PATH) - val modules = path.listFiles { _, name -> name != "lost+found" && name != ".core" } - for (file in modules!!) { - if (file.isFile) continue - val module = OldModule(Const.MAGISK_PATH + "/" + file.name) - moduleMap[module.id] = module - } - return moduleMap - } - fun showSuperUser(): Boolean { return Shell.rootAccess() && (Const.USER_ID == 0 || Config.suMultiuserMode != Config.Value.MULTIUSER_MODE_OWNER_MANAGED) diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/XAndroid.kt b/app/src/main/java/com/topjohnwu/magisk/utils/XAndroid.kt index a7272c2c9..a964adb07 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/XAndroid.kt +++ b/app/src/main/java/com/topjohnwu/magisk/utils/XAndroid.kt @@ -7,6 +7,7 @@ import android.content.pm.ComponentInfo import android.content.pm.PackageInfo import android.content.pm.PackageManager import android.content.pm.PackageManager.* +import android.database.Cursor import android.net.Uri import android.provider.OpenableColumns import com.topjohnwu.magisk.App @@ -104,3 +105,9 @@ fun String.toFile() = File(this) fun Intent.chooser(title: String = "Pick an app") = Intent.createChooser(this, title) fun Context.cachedFile(name: String) = File(cacheDir, name) + +fun Cursor.toList(transformer: (Cursor) -> Result): List { + val out = mutableListOf() + while (moveToNext()) out.add(transformer(this)) + return out +} diff --git a/app/src/main/res/layout/item_module.xml b/app/src/main/res/layout/item_module.xml index 24546703c..91d9f2e48 100644 --- a/app/src/main/res/layout/item_module.xml +++ b/app/src/main/res/layout/item_module.xml @@ -48,7 +48,7 @@ android:id="@+id/version_name" android:layout_width="0dp" android:layout_height="wrap_content" - android:text="@{item.item.version == null || item.item.version.length == 0 ? @string/no_info_provided : item.item.version}" + android:text="@{item.item.version.length == 0 ? @string/no_info_provided : item.item.version}" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="@android:color/tertiary_text_dark" android:textIsSelectable="false" @@ -63,7 +63,7 @@ android:id="@+id/author" android:layout_width="0dp" android:layout_height="wrap_content" - android:text="@{item.item.author == null || item.item.author.length == 0 ? @string/no_info_provided : @string/author(item.item.author)}" + android:text="@{item.item.author.length == 0 ? @string/no_info_provided : @string/author(item.item.author)}" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="@android:color/tertiary_text_dark" android:textIsSelectable="false" @@ -79,7 +79,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center_vertical" - android:text="@{item.item.description == null || item.item.description.length == 0 ? @string/no_info_provided : item.item.description}" + android:text="@{item.item.description.length == 0 ? @string/no_info_provided : item.item.description}" android:textAppearance="?android:attr/textAppearanceSmall" android:textIsSelectable="false" app:layout_constraintBottom_toTopOf="@+id/notice"