Also download to external storage

This commit is contained in:
topjohnwu 2021-01-27 04:09:07 -08:00
parent 2a5f5b1bba
commit eaf4d8064b
6 changed files with 52 additions and 31 deletions

View File

@ -8,10 +8,8 @@ import androidx.lifecycle.MutableLiveData
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.ForegroundTracker import com.topjohnwu.magisk.core.ForegroundTracker
import com.topjohnwu.magisk.core.base.BaseService import com.topjohnwu.magisk.core.base.BaseService
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.core.utils.ProgressInputStream import com.topjohnwu.magisk.core.utils.ProgressInputStream
import com.topjohnwu.magisk.data.repository.NetworkService import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.ktx.withStreams
import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Notifications
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -72,11 +70,7 @@ abstract class BaseDownloader : BaseService(), KoinComponent {
when (this) { when (this) {
is Subject.Module -> // Download and process on-the-fly is Subject.Module -> // Download and process on-the-fly
stream.toModule(file, service.fetchInstaller().byteStream()) stream.toModule(file, service.fetchInstaller().byteStream())
else -> { is Subject.Manager -> handleAPK(this, stream)
withStreams(stream, file.outputStream()) { it, out -> it.copyTo(out) }
if (this is Subject.Manager)
handleAPK(this)
}
} }
val newId = notifyFinish(this) val newId = notifyFinish(this)
if (ForegroundTracker.hasForeground) if (ForegroundTracker.hasForeground)

View File

@ -7,9 +7,13 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.isRunningAsStub import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.tasks.HideAPK import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.ktx.relaunchApp import com.topjohnwu.magisk.ktx.relaunchApp
import com.topjohnwu.magisk.ktx.withStreams
import com.topjohnwu.magisk.ktx.writeTo import com.topjohnwu.magisk.ktx.writeTo
import java.io.File import java.io.File
import java.io.InputStream
import java.io.OutputStream
private fun Context.patch(apk: File) { private fun Context.patch(apk: File) {
val patched = File(apk.parent, "patched.apk") val patched = File(apk.parent, "patched.apk")
@ -26,22 +30,46 @@ private fun BaseDownloader.notifyHide(id: Int) {
} }
} }
suspend fun BaseDownloader.handleAPK(subject: Subject.Manager) { private class DupOutputStream(
if (!isRunningAsStub) private val o1: OutputStream,
return private val o2: OutputStream
val apk = subject.file.toFile() ) : OutputStream() {
val id = subject.notifyID() override fun write(b: Int) {
// Move to upgrade location o1.write(b)
apk.copyTo(DynAPK.update(this), overwrite = true) o2.write(b)
apk.delete() }
if (Info.stub!!.version < subject.stub.versionCode) { override fun write(b: ByteArray?, off: Int, len: Int) {
notifyHide(id) o1.write(b, off, len)
// Also upgrade stub o2.write(b, off, len)
service.fetchFile(subject.stub.link).byteStream().writeTo(apk) }
patch(apk) override fun close() {
} else { o1.close()
// Simply relaunch the app o2.close()
stopSelf() }
relaunchApp(this) }
suspend fun BaseDownloader.handleAPK(subject: Subject.Manager, stream: InputStream) {
fun write(output: OutputStream) {
val ext = subject.externalFile.outputStream()
val o = DupOutputStream(ext, output)
withStreams(stream, o) { src, out -> src.copyTo(out) }
}
if (isRunningAsStub) {
val apk = subject.file.toFile()
val id = subject.notifyID()
write(DynAPK.update(this).outputStream())
if (Info.stub!!.version < subject.stub.versionCode) {
// Also upgrade stub
notifyHide(id)
service.fetchFile(subject.stub.link).byteStream().writeTo(apk)
patch(apk)
} else {
// Simply relaunch the app
stopSelf()
relaunchApp(this)
}
} else {
write(subject.file.outputStream())
} }
} }

View File

@ -1,8 +1,9 @@
package com.topjohnwu.magisk.core.download package com.topjohnwu.magisk.core.download
import android.net.Uri import android.net.Uri
import com.topjohnwu.magisk.ktx.withStreams
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.ktx.forEach
import com.topjohnwu.magisk.ktx.withStreams
import java.io.InputStream import java.io.InputStream
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
@ -25,8 +26,7 @@ fun InputStream.toModule(file: Uri, installer: InputStream) {
zout.write("#MAGISK\n".toByteArray(charset("UTF-8"))) zout.write("#MAGISK\n".toByteArray(charset("UTF-8")))
var off = -1 var off = -1
var entry: ZipEntry? = zin.nextEntry zin.forEach { entry ->
while (entry != null) {
if (off < 0) { if (off < 0) {
off = entry.name.indexOf('/') + 1 off = entry.name.indexOf('/') + 1
} }
@ -38,8 +38,6 @@ fun InputStream.toModule(file: Uri, installer: InputStream) {
zin.copyTo(zout) zin.copyTo(zout)
} }
} }
entry = zin.nextEntry
} }
} }
} }

View File

@ -51,6 +51,7 @@ sealed class Subject : Parcelable {
cachedFile("manager.apk") cachedFile("manager.apk")
} }
val externalFile get() = MediaStoreUtils.getFile("$title.apk").uri
} }
} }

View File

@ -11,7 +11,7 @@ import java.util.*
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
fun ZipInputStream.forEach(callback: (ZipEntry) -> Unit) { inline fun ZipInputStream.forEach(callback: (ZipEntry) -> Unit) {
var entry: ZipEntry? = nextEntry var entry: ZipEntry? = nextEntry
while (entry != null) { while (entry != null) {
callback(entry) callback(entry)

View File

@ -114,7 +114,7 @@ class HomeViewModel(
fun onDeletePressed() = UninstallDialog().publish() fun onDeletePressed() = UninstallDialog().publish()
fun onManagerPressed() = when (state) { fun onManagerPressed() = when (state) {
State.LOADED -> ManagerInstallDialog().publish() State.LOADED -> withExternalRW { ManagerInstallDialog().publish() }
State.LOADING -> SnackbarEvent(R.string.loading).publish() State.LOADING -> SnackbarEvent(R.string.loading).publish()
else -> SnackbarEvent(R.string.no_connection).publish() else -> SnackbarEvent(R.string.no_connection).publish()
} }