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 {
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"
......@@ -95,7 +96,10 @@ object Config : PreferenceModel, DBConfig {
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()
}
......
......@@ -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)
......
......@@ -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
......
......@@ -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,
......
......@@ -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
}
......@@ -212,8 +222,11 @@ class SettingsFragment : BasePreferenceFragment() {
val names = mutableListOf<String>()
val values = mutableListOf<String>()
names.add(LocaleManager.getString(
LocaleManager.defaultLocale, R.string.system_default))
names.add(
LocaleManager.getString(
LocaleManager.defaultLocale, R.string.system_default
)
)
values.add("")
it.forEach { locale ->
......@@ -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()
}
}
......@@ -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 @@
<string name="download_progress">%1$.2f / %2$.2f MB</string>
<string name="download_module">Injecting installer…</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="manager_update_title">Magisk Manager Update Available!</string>
......@@ -126,11 +127,13 @@
<string name="dl_one_module">Download one module at a time.</string>
<!--Settings Activity -->
<string name="settings_downloads_category">Downloads</string>
<string name="settings_general_category">General</string>
<string name="settings_dark_theme_title">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_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_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>
......@@ -192,6 +195,7 @@
<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="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-->
<string name="su_request_title">Superuser Request</string>
......
<androidx.preference.PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:key="general"
android:title="@string/settings_general_category">
<SwitchPreferenceCompat
android:key="dark_theme"
android:defaultValue="true"
android:title="@string/settings_dark_theme_title"
android:summary="@string/settings_dark_theme_summary" />
android:key="dark_theme"
android:summary="@string/settings_dark_theme_summary"
android:title="@string/settings_dark_theme_title" />
<ListPreference
android:key="locale"
android:defaultValue="@string/empty"
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" />
android:key="locale"
android:title="@string/language" />
<Preference
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
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
android:key="restore"
......@@ -38,21 +31,37 @@
</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
android:key="update"
android:title="@string/settings_update">
<SwitchPreferenceCompat
android:key="check_update"
android:defaultValue="true"
android:title="@string/settings_check_update_title"
android:summary="@string/settings_check_update_summary" />
android:key="check_update"
android:summary="@string/settings_check_update_summary"
android:title="@string/settings_check_update_title" />
<ListPreference
android:key="update_channel"
android:title="@string/settings_update_channel_title"
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>
......@@ -62,18 +71,18 @@
<SwitchPreferenceCompat
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
android:key="magiskhide"
android:title="@string/magiskhide"
android:summary="@string/settings_magiskhide_summary" />
android:summary="@string/settings_magiskhide_summary"
android:title="@string/magiskhide" />
<Preference
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>
......@@ -82,55 +91,55 @@
android:title="@string/superuser">
<ListPreference
android:key="root_access"
android:title="@string/superuser_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
android:key="multiuser_mode"
android:title="@string/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
android:key="mnt_ns"
android:title="@string/mount_namespace_mode"
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
android:key="su_auto_response"
android:title="@string/auto_response"
android:defaultValue="0"
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
android:key="su_request_timeout"
android:title="@string/request_timeout"
android:defaultValue="10"
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
android:key="su_notification"
android:title="@string/superuser_notification"
android:defaultValue="1"
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
android:key="su_fingerprint"
android:defaultValue="false"
android:title="@string/settings_su_fingerprint_title"
android:summary="@string/settings_su_fingerprint_summary"/>
android:key="su_fingerprint"
android:summary="@string/settings_su_fingerprint_summary"
android:title="@string/settings_su_fingerprint_title" />
<SwitchPreferenceCompat
android:key="su_reauth"
android:defaultValue="false"
android:title="@string/settings_su_reauth_title"
android:summary="@string/settings_su_reauth_summary"/>
android:key="su_reauth"
android:summary="@string/settings_su_reauth_summary"
android:title="@string/settings_su_reauth_title" />
</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