Commit e5118418 authored by Viktor De Pasquale's avatar Viktor De Pasquale Committed by John Wu

Added option to have custom download location

The location is automatically added to list of supported paths for caching
parent 7cd814d9
...@@ -43,6 +43,7 @@ object Config : PreferenceModel, DBConfig { ...@@ -43,6 +43,7 @@ object Config : PreferenceModel, DBConfig {
const val REPO_ORDER = "repo_order" const val REPO_ORDER = "repo_order"
const val SHOW_SYSTEM_APP = "show_system" const val SHOW_SYSTEM_APP = "show_system"
const val DOWNLOAD_CACHE = "download_cache" const val DOWNLOAD_CACHE = "download_cache"
const val DOWNLOAD_PATH = "download_path"
// system state // system state
const val MAGISKHIDE = "magiskhide" const val MAGISKHIDE = "magiskhide"
...@@ -95,7 +96,10 @@ object Config : PreferenceModel, DBConfig { ...@@ -95,7 +96,10 @@ object Config : PreferenceModel, DBConfig {
if (Utils.isCanary) Value.CANARY_DEBUG_CHANNEL if (Utils.isCanary) Value.CANARY_DEBUG_CHANNEL
else Value.DEFAULT_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 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 repoOrder by preference(Key.REPO_ORDER, Value.ORDER_DATE)
var suDefaultTimeout by preferenceStrInt(Key.SU_REQUEST_TIMEOUT, 10) var suDefaultTimeout by preferenceStrInt(Key.SU_REQUEST_TIMEOUT, 10)
...@@ -123,6 +127,15 @@ object Config : PreferenceModel, DBConfig { ...@@ -123,6 +127,15 @@ object Config : PreferenceModel, DBConfig {
@JvmStatic @JvmStatic
var suManager by dbStrings(Key.SU_MANAGER, "") 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 { fun initialize() = prefs.edit {
val config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS) val config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS)
if (config.exists()) runCatching { if (config.exists()) runCatching {
...@@ -190,8 +203,10 @@ object Config : PreferenceModel, DBConfig { ...@@ -190,8 +203,10 @@ object Config : PreferenceModel, DBConfig {
fun export() { fun export() {
// Flush prefs to disk // Flush prefs to disk
prefs.edit().apply() prefs.edit().apply()
val xml = File("${get<Context>(Protected).filesDir.parent}/shared_prefs", val xml = File(
"${packageName}_preferences.xml") "${get<Context>(Protected).filesDir.parent}/shared_prefs",
"${packageName}_preferences.xml"
)
Shell.su("cat $xml > /data/adb/${Const.MANAGER_CONFIGS}").exec() Shell.su("cat $xml > /data/adb/${Const.MANAGER_CONFIGS}").exec()
} }
......
...@@ -11,6 +11,7 @@ import android.widget.Toast ...@@ -11,6 +11,7 @@ import android.widget.Toast
import androidx.annotation.RequiresPermission import androidx.annotation.RequiresPermission
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import com.topjohnwu.magisk.ClassMap import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.model.entity.internal.Configuration.* import com.topjohnwu.magisk.model.entity.internal.Configuration.*
...@@ -29,7 +30,7 @@ import kotlin.random.Random.Default.nextInt ...@@ -29,7 +30,7 @@ import kotlin.random.Random.Default.nextInt
open class DownloadService : RemoteFileService() { open class DownloadService : RemoteFileService() {
private val context get() = this 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 private val File.type
get() = MimeTypeMap.getSingleton() get() = MimeTypeMap.getSingleton()
.getMimeTypeFromExtension(extension) .getMimeTypeFromExtension(extension)
...@@ -100,19 +101,36 @@ open class DownloadService : RemoteFileService() { ...@@ -100,19 +101,36 @@ open class DownloadService : RemoteFileService() {
// --- // ---
private fun moveToDownloads(file: File) { 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) { if (file != destination) {
destination.deleteRecursively() destination.deleteRecursively()
file.copyTo(destination) file.copyTo(destination)
} }
Utils.toast( Utils.toast(
getString(R.string.internal_storage, "/Download/${file.name}"), getString(
R.string.internal_storage,
"/" + destination.toRelativeString(Const.EXTERNAL_PATH.parentFile)
),
Toast.LENGTH_LONG Toast.LENGTH_LONG
) )
} }
private fun fileIntent(fileName: String): Intent { 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) return Intent(Intent.ACTION_VIEW)
.setDataAndType(file.provide(this), file.type) .setDataAndType(file.provide(this), file.type)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
......
...@@ -9,6 +9,7 @@ import com.topjohnwu.magisk.R ...@@ -9,6 +9,7 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.data.repository.FileRepository 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.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.utils.writeToCachedFile
import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.superuser.ShellUtils import com.topjohnwu.superuser.ShellUtils
...@@ -25,6 +26,13 @@ abstract class RemoteFileService : NotificationService() { ...@@ -25,6 +26,13 @@ abstract class RemoteFileService : NotificationService() {
private val repo by inject<FileRepository>() private val repo by inject<FileRepository>()
private val supportedFolders
get() = listOfNotNull(
cacheDir,
Config.downloadsFile(),
Const.EXTERNAL_PATH
)
override val defaultNotification: NotificationCompat.Builder override val defaultNotification: NotificationCompat.Builder
get() = Notifications get() = Notifications
.progress(this, "") .progress(this, "")
...@@ -53,15 +61,7 @@ abstract class RemoteFileService : NotificationService() { ...@@ -53,15 +61,7 @@ abstract class RemoteFileService : NotificationService() {
throw IllegalStateException("The download cache is disabled") throw IllegalStateException("The download cache is disabled")
} }
val file = runCatching { val file = supportedFolders.firstMap { it.find(subject.fileName) }
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) }
}
if (subject is Magisk) { if (subject is Magisk) {
if (!ShellUtils.checkSum("MD5", file, subject.magisk.hash)) { if (!ShellUtils.checkSum("MD5", file, subject.magisk.hash)) {
...@@ -91,6 +91,10 @@ abstract class RemoteFileService : NotificationService() { ...@@ -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 { private fun ResponseBody.toFile(id: Int, name: String): File {
val maxRaw = contentLength() val maxRaw = contentLength()
val max = maxRaw / 1_000_000f val max = maxRaw / 1_000_000f
......
...@@ -20,6 +20,7 @@ abstract class BasePreferenceFragment : PreferenceFragmentCompat(), ...@@ -20,6 +20,7 @@ abstract class BasePreferenceFragment : PreferenceFragmentCompat(),
protected val prefs: SharedPreferences by inject() protected val prefs: SharedPreferences by inject()
protected val app: App by inject() protected val app: App by inject()
protected val activity get() = requireActivity() as MagiskActivity<*, *>
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
......
...@@ -83,24 +83,34 @@ class SettingsFragment : BasePreferenceFragment() { ...@@ -83,24 +83,34 @@ class SettingsFragment : BasePreferenceFragment() {
true 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 -> updateChannel.setOnPreferenceChangeListener { _, value ->
val channel = Integer.parseInt(value as String) val channel = Integer.parseInt(value as String)
val previous = Config.updateChannel val previous = Config.updateChannel
if (channel == Config.Value.CUSTOM_CHANNEL) { if (channel == Config.Value.CUSTOM_CHANNEL) {
val v = LayoutInflater.from(requireActivity()) showUrlDialog(Config.customChannelUrl, {
.inflate(R.layout.custom_channel_dialog, null) Config.updateChannel = previous
val url = v.findViewById<EditText>(R.id.custom_url) }, {
url.setText(Config.customChannelUrl) Config.customChannelUrl = it
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()
} }
true true
} }
...@@ -212,8 +222,11 @@ class SettingsFragment : BasePreferenceFragment() { ...@@ -212,8 +222,11 @@ class SettingsFragment : BasePreferenceFragment() {
val names = mutableListOf<String>() val names = mutableListOf<String>()
val values = mutableListOf<String>() val values = mutableListOf<String>()
names.add(LocaleManager.getString( names.add(
LocaleManager.defaultLocale, R.string.system_default)) LocaleManager.getString(
LocaleManager.defaultLocale, R.string.system_default
)
)
values.add("") values.add("")
it.forEach { locale -> it.forEach { locale ->
...@@ -262,4 +275,26 @@ class SettingsFragment : BasePreferenceFragment() { ...@@ -262,4 +275,26 @@ class SettingsFragment : BasePreferenceFragment() {
setSummary(Config.Key.SU_NOTIFICATION) setSummary(Config.Key.SU_NOTIFICATION)
setSummary(Config.Key.SU_REQUEST_TIMEOUT) 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()
}
} }
...@@ -36,3 +36,10 @@ inline fun <In : InputStream, Out : OutputStream> withStreams( ...@@ -36,3 +36,10 @@ inline fun <In : InputStream, Out : OutputStream> withStreams(
} }
} }
} }
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.")
}
\ No newline at end of file
...@@ -75,6 +75,7 @@ ...@@ -75,6 +75,7 @@
<string name="download_progress">%1$.2f / %2$.2f MB</string> <string name="download_progress">%1$.2f / %2$.2f MB</string>
<string name="download_module">Injecting installer…</string> <string name="download_module">Injecting installer…</string>
<string name="download_file_error">Error downloading file</string> <string name="download_file_error">Error downloading file</string>
<string name="download_file_folder_error">Unable to fetch parent folder in order to save the downloaded file, check permissions.</string>
<string name="magisk_update_title">Magisk Update Available!</string> <string name="magisk_update_title">Magisk Update Available!</string>
<string name="manager_update_title">Magisk Manager Update Available!</string> <string name="manager_update_title">Magisk Manager Update Available!</string>
...@@ -126,11 +127,13 @@ ...@@ -126,11 +127,13 @@
<string name="dl_one_module">Download one module at a time.</string> <string name="dl_one_module">Download one module at a time.</string>
<!--Settings Activity --> <!--Settings Activity -->
<string name="settings_downloads_category">Downloads</string>
<string name="settings_general_category">General</string> <string name="settings_general_category">General</string>
<string name="settings_dark_theme_title">Dark Theme</string> <string name="settings_dark_theme_title">Dark Theme</string>
<string name="settings_dark_theme_summary">Enable dark theme.</string> <string name="settings_dark_theme_summary">Enable dark theme.</string>
<string name="settings_download_cache_title">Download Cache</string> <string name="settings_download_cache_title">Download Cache</string>
<string name="settings_download_cache_summary">Enables download cache for Magisk and Module zip files.</string> <string name="settings_download_cache_summary">Enables download cache for Magisk and Module zip files.</string>
<string name="settings_download_path_title">Download path</string>
<string name="settings_clear_cache_title">Clear Repo Cache</string> <string name="settings_clear_cache_title">Clear Repo Cache</string>
<string name="settings_clear_cache_summary">Clear the cached information for online repos. This forces the app to refresh online.</string> <string name="settings_clear_cache_summary">Clear the cached information for online repos. This forces the app to refresh online.</string>
<string name="settings_hide_manager_title">Hide Magisk Manager</string> <string name="settings_hide_manager_title">Hide Magisk Manager</string>
...@@ -192,6 +195,7 @@ ...@@ -192,6 +195,7 @@
<string name="isolate_summary">Each root session will have its own isolated namespace.</string> <string name="isolate_summary">Each root session will have its own isolated namespace.</string>
<string name="android_o_not_support">Does not support Android 8.0+.</string> <string name="android_o_not_support">Does not support Android 8.0+.</string>
<string name="disable_fingerprint">No fingerprints were set or no device support.</string> <string name="disable_fingerprint">No fingerprints were set or no device support.</string>
<string name="settings_download_path_error">Error creating folder. It must be accessible from storage root directory and must not be a file.</string>
<!--Superuser--> <!--Superuser-->
<string name="su_request_title">Superuser Request</string> <string name="su_request_title">Superuser Request</string>
......
<androidx.preference.PreferenceScreen <androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory <PreferenceCategory
android:key="general" android:key="general"
android:title="@string/settings_general_category"> android:title="@string/settings_general_category">
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:key="dark_theme"
android:defaultValue="true" android:defaultValue="true"
android:title="@string/settings_dark_theme_title" android:key="dark_theme"
android:summary="@string/settings_dark_theme_summary" /> android:summary="@string/settings_dark_theme_summary"
android:title="@string/settings_dark_theme_title" />
<ListPreference <ListPreference
android:key="locale"
android:defaultValue="@string/empty" android:defaultValue="@string/empty"
android:title="@string/language"/> android:key="locale"
android:title="@string/language" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="download_cache"
android:summary="@string/settings_download_cache_summary"
android:title="@string/settings_download_cache_title" />
<Preference <Preference
android:key="clear" android:key="clear"
android:title="@string/settings_clear_cache_title" android:summary="@string/settings_clear_cache_summary"
android:summary="@string/settings_clear_cache_summary" /> android:title="@string/settings_clear_cache_title" />
<Preference <Preference
android:key="hide" android:key="hide"
android:title="@string/settings_hide_manager_title" android:summary="@string/settings_hide_manager_summary"
android:summary="@string/settings_hide_manager_summary" /> android:title="@string/settings_hide_manager_title" />
<Preference <Preference
android:key="restore" android:key="restore"
...@@ -38,21 +31,37 @@ ...@@ -38,21 +31,37 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory
android:key="downloads"
android:title="@string/settings_downloads_category">
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="download_cache"
android:summary="@string/settings_download_cache_summary"
android:title="@string/settings_download_cache_title" />
<Preference
android:key="download_path"
android:title="@string/settings_download_path_title" />
</PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:key="update" android:key="update"
android:title="@string/settings_update"> android:title="@string/settings_update">
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:key="check_update"
android:defaultValue="true" android:defaultValue="true"
android:title="@string/settings_check_update_title" android:key="check_update"
android:summary="@string/settings_check_update_summary" /> android:summary="@string/settings_check_update_summary"
android:title="@string/settings_check_update_title" />
<ListPreference <ListPreference
android:key="update_channel"
android:title="@string/settings_update_channel_title"
android:entries="@array/update_channel" android:entries="@array/update_channel"
android:entryValues="@array/value_array" /> android:entryValues="@array/value_array"
android:key="update_channel"
android:title="@string/settings_update_channel_title" />
</PreferenceCategory> </PreferenceCategory>
...@@ -62,18 +71,18 @@ ...@@ -62,18 +71,18 @@
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:key="disable" android:key="disable"
android:title="@string/settings_core_only_title" android:summary="@string/settings_core_only_summary"
android:summary="@string/settings_core_only_summary" /> android:title="@string/settings_core_only_title" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:key="magiskhide" android:key="magiskhide"
android:title="@string/magiskhide" android:summary="@string/settings_magiskhide_summary"
android:summary="@string/settings_magiskhide_summary" /> android:title="@string/magiskhide" />
<Preference <Preference
android:key="hosts" android:key="hosts"
android:title="@string/settings_hosts_title" android:summary="@string/settings_hosts_summary"
android:summary="@string/settings_hosts_summary" /> android:title="@string/settings_hosts_title" />
</PreferenceCategory> </PreferenceCategory>
...@@ -82,55 +91,55 @@ ...@@ -82,55 +91,55 @@
android:title="@string/superuser"> android:title="@string/superuser">
<ListPreference <ListPreference
android:key="root_access"
android:title="@string/superuser_access"
android:entries="@array/su_access" android:entries="@array/su_access"
android:entryValues="@array/value_array" /> android:entryValues="@array/value_array"
android:key="root_access"
android:title="@string/superuser_access" />
<ListPreference <ListPreference
android:key="multiuser_mode"
android:title="@string/multiuser_mode"
android:entries="@array/multiuser_mode" android:entries="@array/multiuser_mode"
android:entryValues="@array/value_array" /> android:entryValues="@array/value_array"
android:key="multiuser_mode"
android:title="@string/multiuser_mode" />
<ListPreference <ListPreference
android:key="mnt_ns"
android:title="@string/mount_namespace_mode"
android:entries="@array/namespace" android:entries="@array/namespace"
android:entryValues="@array/value_array" /> android:entryValues="@array/value_array"
android:key="mnt_ns"
android:title="@string/mount_namespace_mode" />
<ListPreference <ListPreference
android:key="su_auto_response"
android:title="@string/auto_response"
android:defaultValue="0" android:defaultValue="0"
android:entries="@array/auto_response" android:entries="@array/auto_response"
android:entryValues="@array/value_array" /> android:entryValues="@array/value_array"
android:key="su_auto_response"
android:title="@string/auto_response" />
<ListPreference <ListPreference
android:key="su_request_timeout"
android:title="@string/request_timeout"
android:defaultValue="10" android:defaultValue="10"
android:entries="@array/request_timeout" android:entries="@array/request_timeout"
android:entryValues="@array/request_timeout_value" /> android:entryValues="@array/request_timeout_value"
android:key="su_request_timeout"
android:title="@string/request_timeout" />
<ListPreference <ListPreference
android:key="su_notification"
android:title="@string/superuser_notification"
android:defaultValue="1" android:defaultValue="1"
android:entries="@array/su_notification" android:entries="@array/su_notification"
android:entryValues="@array/value_array" /> android:entryValues="@array/value_array"
android:key="su_notification"
android:title="@string/superuser_notification" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:key="su_fingerprint"
android:defaultValue="false" android:defaultValue="false"
android:title="@string/settings_su_fingerprint_title" android:key="su_fingerprint"
android:summary="@string/settings_su_fingerprint_summary"/> android:summary="@string/settings_su_fingerprint_summary"
android:title="@string/settings_su_fingerprint_title" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:key="su_reauth"
android:defaultValue="false" android:defaultValue="false"
android:title="@string/settings_su_reauth_title" android:key="su_reauth"
android:summary="@string/settings_su_reauth_summary"/> android:summary="@string/settings_su_reauth_summary"
android:title="@string/settings_su_reauth_title" />
</PreferenceCategory> </PreferenceCategory>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment