diff --git a/app/src/main/java/com/topjohnwu/magisk/model/download/DownloadService.kt b/app/src/main/java/com/topjohnwu/magisk/model/download/DownloadService.kt
index 7ea6c13b3..c68bea1f6 100644
--- a/app/src/main/java/com/topjohnwu/magisk/model/download/DownloadService.kt
+++ b/app/src/main/java/com/topjohnwu/magisk/model/download/DownloadService.kt
@@ -43,6 +43,7 @@ open class DownloadService : RemoteFileService() {
override fun onFinished(file: File, subject: DownloadSubject) = when (subject) {
is Magisk -> onFinishedInternal(file, subject)
is Module -> onFinishedInternal(file, subject)
+ else -> Unit
}
private fun onFinishedInternal(
@@ -73,6 +74,7 @@ open class DownloadService : RemoteFileService() {
) = when (subject) {
is Magisk -> addActionsInternal(file, subject)
is Module -> addActionsInternal(file, subject)
+ else -> this
}
private fun NotificationCompat.Builder.addActionsInternal(
diff --git a/app/src/main/java/com/topjohnwu/magisk/model/download/RemoteFileService.kt b/app/src/main/java/com/topjohnwu/magisk/model/download/RemoteFileService.kt
index f1cbb4711..29b792d9b 100644
--- a/app/src/main/java/com/topjohnwu/magisk/model/download/RemoteFileService.kt
+++ b/app/src/main/java/com/topjohnwu/magisk/model/download/RemoteFileService.kt
@@ -8,14 +8,21 @@ import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.data.repository.FileRepository
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
+import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
+import com.topjohnwu.magisk.utils.cachedFile
+import com.topjohnwu.magisk.utils.withStreams
import com.topjohnwu.magisk.utils.writeToCachedFile
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.superuser.ShellUtils
import io.reactivex.Single
+import io.reactivex.android.schedulers.AndroidSchedulers
import okhttp3.ResponseBody
import org.koin.android.ext.android.inject
import timber.log.Timber
import java.io.File
+import java.util.zip.ZipEntry
+import java.util.zip.ZipInputStream
+import java.util.zip.ZipOutputStream
abstract class RemoteFileService : NotificationService() {
@@ -33,14 +40,17 @@ abstract class RemoteFileService : NotificationService() {
// ---
- private fun start(subject: DownloadSubject) = search(subject)
+ private fun startInternal(subject: DownloadSubject) = search(subject)
.onErrorResumeNext(download(subject))
.doOnSubscribe { update(subject.hashCode()) { it.setContentTitle(subject.fileName) } }
- .subscribeK {
+ .observeOn(AndroidSchedulers.mainThread())
+ .doOnSuccess {
runCatching { onFinished(it, subject) }.onFailure { Timber.e(it) }
finish(it, subject)
}
+ private fun start(subject: DownloadSubject) = startInternal(subject).subscribeK()
+
private fun search(subject: DownloadSubject) = Single.fromCallable {
if (!Config.isDownloadCacheEnabled) {
throw IllegalStateException("The download cache is disabled")
@@ -56,7 +66,7 @@ abstract class RemoteFileService : NotificationService() {
.let { File(Const.EXTERNAL_PATH, it) }
}
- if (subject is DownloadSubject.Magisk) {
+ if (subject is Magisk) {
if (!ShellUtils.checkSum("MD5", file, subject.magisk.hash)) {
throw IllegalStateException("The given file doesn't match the hash")
}
@@ -66,8 +76,57 @@ abstract class RemoteFileService : NotificationService() {
}
private fun download(subject: DownloadSubject) = repo.downloadFile(subject.url)
- .map { it.toFile(subject.hashCode(), subject.fileName) }
- .map { map(subject, it) }
+ .map {
+ when (subject) {
+ is Module -> appendInstaller(it, subject)
+ else -> it.toFile(subject.hashCode(), subject.fileName)
+ }
+ }
+
+ private fun appendInstaller(body: ResponseBody, subject: DownloadSubject): File {
+ update(subject.hashCode()) {
+ it.setContentText(getString(R.string.download_module))
+ }
+
+ val installer = startInternal(Installer).blockingGet()
+ val target = cachedFile(subject.fileName)
+
+ val input = ZipInputStream(body.byteStream())
+ val output = ZipOutputStream(target.outputStream())
+
+ withStreams(input, output) { zin, zout ->
+ zout.putNextEntry(ZipEntry("META-INF/"))
+ zout.putNextEntry(ZipEntry("META-INF/com/"))
+ zout.putNextEntry(ZipEntry("META-INF/com/google/"))
+ zout.putNextEntry(ZipEntry("META-INF/com/google/android/"))
+ zout.putNextEntry(ZipEntry("META-INF/com/google/android/update-binary"))
+ installer.inputStream().copyTo(zout).also { zout.flush() }
+
+ zout.putNextEntry(ZipEntry("META-INF/com/google/android/updater-script"))
+ zout.write("#MAGISK\n".toByteArray(charset("UTF-8")))
+
+ var off = -1
+ var entry: ZipEntry? = zin.nextEntry
+ while (entry != null) {
+ Timber.i("Let's gooo (${entry.name})")
+ if (off < 0) {
+ off = entry.name.indexOf('/') + 1
+ }
+
+ val path = entry.name.substring(off)
+ if (path.isNotEmpty() && !path.startsWith("META-INF")) {
+ zout.putNextEntry(ZipEntry(path))
+ if (!entry.isDirectory) {
+ zin.copyTo(zout).also { zout.flush() }
+ }
+ }
+
+ entry = zin.nextEntry
+ }
+ }
+
+ return target
+ }
// ---
@@ -87,6 +146,8 @@ abstract class RemoteFileService : NotificationService() {
}
private fun finish(file: File, subject: DownloadSubject) = finishWork(subject.hashCode()) {
+ if (subject is Installer) return@finishWork null
+
it.addActions(file, subject)
.setContentText(getString(R.string.download_complete))
.setSmallIcon(android.R.drawable.stat_sys_download_done)
diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/internal/DownloadSubject.kt b/app/src/main/java/com/topjohnwu/magisk/model/entity/internal/DownloadSubject.kt
index 9be5934b8..028b5330f 100644
--- a/app/src/main/java/com/topjohnwu/magisk/model/entity/internal/DownloadSubject.kt
+++ b/app/src/main/java/com/topjohnwu/magisk/model/entity/internal/DownloadSubject.kt
@@ -1,6 +1,8 @@
package com.topjohnwu.magisk.model.entity.internal
import android.os.Parcelable
+import com.topjohnwu.magisk.BuildConfig
+import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.model.entity.MagiskJson
import com.topjohnwu.magisk.model.entity.Repo
@@ -33,4 +35,10 @@ sealed class DownloadSubject : Parcelable {
}
+ @Parcelize
+ object Installer : DownloadSubject() {
+ override val fileName: String get() = "module_installer(${BuildConfig.VERSION_CODE}).sh"
+ override val url: String get() = Const.Url.MODULE_INSTALLER
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/XNetwork.kt b/app/src/main/java/com/topjohnwu/magisk/utils/XNetwork.kt
index df7c91c41..591ae5187 100644
--- a/app/src/main/java/com/topjohnwu/magisk/utils/XNetwork.kt
+++ b/app/src/main/java/com/topjohnwu/magisk/utils/XNetwork.kt
@@ -10,16 +10,30 @@ inline fun ResponseBody.writeToCachedFile(
context: Context,
fileName: String,
progress: (Long) -> Unit = {}
-): File {
- val file = File(context.cacheDir, fileName)
- withStreams(byteStream(), file.outputStream()) { inStream, outStream ->
+): File = byteStream().writeToCachedFile(context, fileName, progress)
+
+inline fun InputStream.writeToCachedFile(
+ context: Context,
+ fileName: String,
+ progress: (Long) -> Unit = {}
+) = context.cachedFile(fileName).apply {
+ writeToFile(this, progress)
+}
+
+inline fun InputStream.writeToFile(file: File, progress: (Long) -> Unit = {}) = file.apply {
+ writeTo(file.outputStream(), progress)
+}
+
+inline fun InputStream.writeTo(output: OutputStream, progress: (Long) -> Unit = {}) {
+ withStreams(this, output) { inStream, outStream ->
inStream.copyToWithProgress(outStream, progress)
}
- return file
}
fun ResponseBody.writeToString() = string()
+fun Context.cachedFile(name: String) = File(cacheDir, name)
+
inline fun InputStream.copyToWithProgress(
out: OutputStream,
progressEmitter: (Long) -> Unit,
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 92e51ddb8..baebfea75 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -73,6 +73,7 @@
Download complete
Looking for local copies…
%1$.2f / %2$.2f MB
+ Injecting installer…
Error downloading file
Magisk Update Available!
Magisk Manager Update Available!