Skip to content

Commit

Permalink
Added option to have custom download location
Browse files Browse the repository at this point in the history
The location is automatically added to list of supported paths for caching
  • Loading branch information
diareuse authored and John Wu committed Jul 20, 2019
1 parent 7cd814d commit e511841
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 107 deletions.
23 changes: 19 additions & 4 deletions app/src/main/java/com/topjohnwu/magisk/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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, "")
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
109 changes: 72 additions & 37 deletions app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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("")

it.forEach { locale ->
names.add(locale.getDisplayName(locale))
values.add(LocaleManager.toLanguageTag(locale))
}
names.add(
LocaleManager.getString(
LocaleManager.defaultLocale, R.string.system_default
)
)
values.add("")

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)
}
}

Expand All @@ -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()
}
}
7 changes: 7 additions & 0 deletions app/src/main/java/com/topjohnwu/magisk/utils/XJava.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
}
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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>
Expand Down
Loading

0 comments on commit e511841

Please sign in to comment.