Added option to have custom download location

The location is automatically added to list of supported paths for caching
This commit is contained in:
Viktor De Pasquale
2019-07-13 13:26:33 +02:00
committed by John Wu
parent 7cd814d917
commit e5118418b2
8 changed files with 200 additions and 107 deletions

View File

@@ -43,6 +43,7 @@ object Config : PreferenceModel, DBConfig {
const val REPO_ORDER = "repo_order"
const val SHOW_SYSTEM_APP = "show_system"
const val DOWNLOAD_CACHE = "download_cache"
const val DOWNLOAD_PATH = "download_path"
// system state
const val MAGISKHIDE = "magiskhide"
@@ -92,10 +93,13 @@ object Config : PreferenceModel, DBConfig {
}
private val defaultChannel =
if (Utils.isCanary) Value.CANARY_DEBUG_CHANNEL
else Value.DEFAULT_CHANNEL
if (Utils.isCanary) Value.CANARY_DEBUG_CHANNEL
else Value.DEFAULT_CHANNEL
private val defaultDownloadPath get() = Const.EXTERNAL_PATH.toRelativeString(Const.EXTERNAL_PATH.parentFile)
var isDownloadCacheEnabled by preference(Key.DOWNLOAD_CACHE, true)
var downloadPath by preference(Key.DOWNLOAD_PATH, defaultDownloadPath)
var repoOrder by preference(Key.REPO_ORDER, Value.ORDER_DATE)
var suDefaultTimeout by preferenceStrInt(Key.SU_REQUEST_TIMEOUT, 10)
@@ -123,6 +127,15 @@ object Config : PreferenceModel, DBConfig {
@JvmStatic
var suManager by dbStrings(Key.SU_MANAGER, "")
fun downloadsFile(path: String = downloadPath) =
File(Const.EXTERNAL_PATH.parentFile, path).run {
if (exists()) {
if (isDirectory) this else null
} else {
if (mkdirs()) this else null
}
}
fun initialize() = prefs.edit {
val config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS)
if (config.exists()) runCatching {
@@ -190,8 +203,10 @@ object Config : PreferenceModel, DBConfig {
fun export() {
// Flush prefs to disk
prefs.edit().apply()
val xml = File("${get<Context>(Protected).filesDir.parent}/shared_prefs",
"${packageName}_preferences.xml")
val xml = File(
"${get<Context>(Protected).filesDir.parent}/shared_prefs",
"${packageName}_preferences.xml"
)
Shell.su("cat $xml > /data/adb/${Const.MANAGER_CONFIGS}").exec()
}

View File

@@ -11,6 +11,7 @@ import android.widget.Toast
import androidx.annotation.RequiresPermission
import androidx.core.app.NotificationCompat
import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.model.entity.internal.Configuration.*
@@ -29,7 +30,7 @@ import kotlin.random.Random.Default.nextInt
open class DownloadService : RemoteFileService() {
private val context get() = this
private val String.downloadsFile get() = File(Const.EXTERNAL_PATH, this)
private val String.downloadsFile get() = Config.downloadsFile()?.let { File(it, this) }
private val File.type
get() = MimeTypeMap.getSingleton()
.getMimeTypeFromExtension(extension)
@@ -100,19 +101,36 @@ open class DownloadService : RemoteFileService() {
// ---
private fun moveToDownloads(file: File) {
val destination = file.name.downloadsFile
val destination = file.name.downloadsFile ?: let {
Utils.toast(
getString(R.string.download_file_folder_error),
Toast.LENGTH_LONG
)
return
}
if (file != destination) {
destination.deleteRecursively()
file.copyTo(destination)
}
Utils.toast(
getString(R.string.internal_storage, "/Download/${file.name}"),
getString(
R.string.internal_storage,
"/" + destination.toRelativeString(Const.EXTERNAL_PATH.parentFile)
),
Toast.LENGTH_LONG
)
}
private fun fileIntent(fileName: String): Intent {
val file = fileName.downloadsFile
val file = fileName.downloadsFile ?: let {
Utils.toast(
getString(R.string.download_file_folder_error),
Toast.LENGTH_LONG
)
return Intent()
}
return Intent(Intent.ACTION_VIEW)
.setDataAndType(file.provide(this), file.type)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

View File

@@ -9,6 +9,7 @@ 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.firstMap
import com.topjohnwu.magisk.utils.writeToCachedFile
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.superuser.ShellUtils
@@ -25,6 +26,13 @@ abstract class RemoteFileService : NotificationService() {
private val repo by inject<FileRepository>()
private val supportedFolders
get() = listOfNotNull(
cacheDir,
Config.downloadsFile(),
Const.EXTERNAL_PATH
)
override val defaultNotification: NotificationCompat.Builder
get() = Notifications
.progress(this, "")
@@ -53,15 +61,7 @@ abstract class RemoteFileService : NotificationService() {
throw IllegalStateException("The download cache is disabled")
}
val file = runCatching {
cacheDir.list().orEmpty()
.first { it == subject.fileName } // this throws an exception if not found
.let { File(cacheDir, it) }
}.getOrElse {
Const.EXTERNAL_PATH.list().orEmpty()
.first { it == subject.fileName } // this throws an exception if not found
.let { File(Const.EXTERNAL_PATH, it) }
}
val file = supportedFolders.firstMap { it.find(subject.fileName) }
if (subject is Magisk) {
if (!ShellUtils.checkSum("MD5", file, subject.magisk.hash)) {
@@ -91,6 +91,10 @@ abstract class RemoteFileService : NotificationService() {
// ---
private fun File.find(name: String) = list().orEmpty()
.firstOrNull { it == name }
?.let { File(this, it) }
private fun ResponseBody.toFile(id: Int, name: String): File {
val maxRaw = contentLength()
val max = maxRaw / 1_000_000f

View File

@@ -20,6 +20,7 @@ abstract class BasePreferenceFragment : PreferenceFragmentCompat(),
protected val prefs: SharedPreferences by inject()
protected val app: App by inject()
protected val activity get() = requireActivity() as MagiskActivity<*, *>
override fun onCreateView(
inflater: LayoutInflater,

View File

@@ -83,24 +83,34 @@ class SettingsFragment : BasePreferenceFragment() {
true
}
findPreference(Config.Key.DOWNLOAD_PATH).apply {
summary = Config.downloadPath
}.setOnPreferenceClickListener { preference ->
activity.withExternalRW {
onSuccess {
showUrlDialog(Config.downloadPath) {
Config.downloadsFile(it)?.let { _ ->
Config.downloadPath = it
preference.summary = it
} ?: let {
Utils.toast(R.string.settings_download_path_error, Toast.LENGTH_SHORT)
}
}
}
}
true
}
updateChannel.setOnPreferenceChangeListener { _, value ->
val channel = Integer.parseInt(value as String)
val previous = Config.updateChannel
if (channel == Config.Value.CUSTOM_CHANNEL) {
val v = LayoutInflater.from(requireActivity())
.inflate(R.layout.custom_channel_dialog, null)
val url = v.findViewById<EditText>(R.id.custom_url)
url.setText(Config.customChannelUrl)
AlertDialog.Builder(requireActivity())
.setTitle(R.string.settings_update_custom)
.setView(v)
.setPositiveButton(R.string.ok) { _, _ ->
Config.customChannelUrl = url.text.toString() }
.setNegativeButton(R.string.close) { _, _ ->
Config.updateChannel = previous }
.setOnCancelListener { Config.updateChannel = previous }
.show()
showUrlDialog(Config.customChannelUrl, {
Config.updateChannel = previous
}, {
Config.customChannelUrl = it
})
}
true
}
@@ -208,48 +218,51 @@ class SettingsFragment : BasePreferenceFragment() {
private fun setLocalePreference(lp: ListPreference) {
lp.isEnabled = false
LocaleManager.availableLocales
.map {
val names = mutableListOf<String>()
val values = mutableListOf<String>()
.map {
val names = mutableListOf<String>()
val values = mutableListOf<String>()
names.add(LocaleManager.getString(
LocaleManager.defaultLocale, R.string.system_default))
values.add("")
names.add(
LocaleManager.getString(
LocaleManager.defaultLocale, R.string.system_default
)
)
values.add("")
it.forEach { locale ->
names.add(locale.getDisplayName(locale))
values.add(LocaleManager.toLanguageTag(locale))
}
Pair(names.toTypedArray(), values.toTypedArray())
}.subscribeK { (names, values) ->
lp.isEnabled = true
lp.entries = names
lp.entryValues = values
lp.summary = LocaleManager.locale.getDisplayName(LocaleManager.locale)
it.forEach { locale ->
names.add(locale.getDisplayName(locale))
values.add(LocaleManager.toLanguageTag(locale))
}
Pair(names.toTypedArray(), values.toTypedArray())
}.subscribeK { (names, values) ->
lp.isEnabled = true
lp.entries = names
lp.entryValues = values
lp.summary = LocaleManager.locale.getDisplayName(LocaleManager.locale)
}
}
private fun setSummary(key: String) {
when (key) {
Config.Key.ROOT_ACCESS -> rootConfig.summary = resources
.getStringArray(R.array.su_access)[Config.rootMode]
.getStringArray(R.array.su_access)[Config.rootMode]
Config.Key.SU_MULTIUSER_MODE -> multiuserConfig.summary = resources
.getStringArray(R.array.multiuser_summary)[Config.suMultiuserMode]
.getStringArray(R.array.multiuser_summary)[Config.suMultiuserMode]
Config.Key.SU_MNT_NS -> nsConfig.summary = resources
.getStringArray(R.array.namespace_summary)[Config.suMntNamespaceMode]
.getStringArray(R.array.namespace_summary)[Config.suMntNamespaceMode]
Config.Key.UPDATE_CHANNEL -> {
var ch = Config.updateChannel
ch = if (ch < 0) Config.Value.STABLE_CHANNEL else ch
updateChannel.summary = resources
.getStringArray(R.array.update_channel)[ch]
.getStringArray(R.array.update_channel)[ch]
}
Config.Key.SU_AUTO_RESPONSE -> autoRes.summary = resources
.getStringArray(R.array.auto_response)[Config.suAutoReponse]
.getStringArray(R.array.auto_response)[Config.suAutoReponse]
Config.Key.SU_NOTIFICATION -> suNotification.summary = resources
.getStringArray(R.array.su_notification)[Config.suNotification]
.getStringArray(R.array.su_notification)[Config.suNotification]
Config.Key.SU_REQUEST_TIMEOUT -> requestTimeout.summary =
getString(R.string.request_timeout_summary, Config.suDefaultTimeout)
getString(R.string.request_timeout_summary, Config.suDefaultTimeout)
}
}
@@ -262,4 +275,26 @@ class SettingsFragment : BasePreferenceFragment() {
setSummary(Config.Key.SU_NOTIFICATION)
setSummary(Config.Key.SU_REQUEST_TIMEOUT)
}
private inline fun showUrlDialog(
initialValue: String,
crossinline onCancel: () -> Unit = {},
crossinline onSuccess: (String) -> Unit
) {
val v = LayoutInflater
.from(requireActivity())
.inflate(R.layout.custom_channel_dialog, null)
val url = v.findViewById<EditText>(R.id.custom_url).apply {
setText(initialValue)
}
AlertDialog.Builder(requireActivity())
.setTitle(R.string.settings_update_custom)
.setView(v)
.setPositiveButton(R.string.ok) { _, _ -> onSuccess(url.text.toString()) }
.setNegativeButton(R.string.close) { _, _ -> onCancel() }
.setOnCancelListener { onCancel() }
.show()
}
}

View File

@@ -35,4 +35,11 @@ inline fun <In : InputStream, Out : OutputStream> withStreams(
withBoth(reader, writer)
}
}
}
inline fun <T, R> List<T>.firstMap(mapper: (T) -> R?): R {
for (item: T in this) {
return mapper(item) ?: continue
}
throw NoSuchElementException("Collection contains no element matching the predicate.")
}