Allow changing thumbnail max size

This commit is contained in:
Matéo Duparc 2022-03-23 16:35:13 +01:00
parent 72321b8ec5
commit 2ee7a5b871
Signed by: hardcoresushi
GPG Key ID: AFE384344A45E13A
15 changed files with 147 additions and 62 deletions

View File

@ -3,45 +3,44 @@ package sushi.hardcore.droidfs
import android.net.Uri import android.net.Uri
import java.io.File import java.io.File
class ConstValues { object ConstValues {
companion object { const val CREATOR = "DroidFS"
const val creator = "DroidFS" const val FILE_MODE = 384 //0600
const val gocryptfsConfFilename = "gocryptfs.conf" const val DIRECTORY_MODE = 448 //0700
const val FILE_MODE = 384 //0600 const val VOLUME_DATABASE_NAME = "SavedVolumes"
const val DIRECTORY_MODE = 448 //0700 const val SORT_ORDER_KEY = "sort_order"
const val volumeDatabaseName = "SavedVolumes" val FAKE_URI: Uri = Uri.parse("fakeuri://droidfs")
const val sort_order_key = "sort_order" const val MAX_KERNEL_WRITE = 128*1024
val fakeUri: Uri = Uri.parse("fakeuri://droidfs") const val WIPE_PASSES = 2
const val MAX_KERNEL_WRITE = 128*1024 const val SLIDESHOW_DELAY: Long = 4000
const val wipe_passes = 2 const val DEFAULT_THEME_VALUE = "dark_green"
const val slideshow_delay: Long = 4000 const val THUMBNAIL_MAX_SIZE_KEY = "thumbnail_max_size"
const val DEFAULT_THEME_VALUE = "dark_green" const val DEFAULT_THUMBNAIL_MAX_SIZE = 10_000L
private val fileExtensions = mapOf( private val FILE_EXTENSIONS = mapOf(
Pair("image", listOf("png", "jpg", "jpeg", "gif", "webp", "bmp")), Pair("image", listOf("png", "jpg", "jpeg", "gif", "webp", "bmp")),
Pair("video", listOf("mp4", "webm", "mkv", "mov")), Pair("video", listOf("mp4", "webm", "mkv", "mov")),
Pair("audio", listOf("mp3", "ogg", "m4a", "wav", "flac")), Pair("audio", listOf("mp3", "ogg", "m4a", "wav", "flac")),
Pair("pdf", listOf("pdf")), Pair("pdf", listOf("pdf")),
Pair("text", listOf("txt", "json", "conf", "log", "xml", "java", "kt", "py", "pl", "rb", "go", "c", "h", "cpp", "hpp", "rs", "sh", "bat", "js", "html", "css", "php", "yml", "yaml", "toml", "ini", "md", "properties")) Pair("text", listOf("txt", "json", "conf", "log", "xml", "java", "kt", "py", "pl", "rb", "go", "c", "h", "cpp", "hpp", "rs", "sh", "bat", "js", "html", "css", "php", "yml", "yaml", "toml", "ini", "md", "properties"))
) )
fun isExtensionType(extensionType: String, path: String): Boolean { fun isExtensionType(extensionType: String, path: String): Boolean {
return fileExtensions[extensionType]?.contains(File(path).extension.lowercase()) ?: false return FILE_EXTENSIONS[extensionType]?.contains(File(path).extension.lowercase()) ?: false
} }
fun isImage(path: String): Boolean { fun isImage(path: String): Boolean {
return isExtensionType("image", path) return isExtensionType("image", path)
} }
fun isVideo(path: String): Boolean { fun isVideo(path: String): Boolean {
return isExtensionType("video", path) return isExtensionType("video", path)
} }
fun isAudio(path: String): Boolean { fun isAudio(path: String): Boolean {
return isExtensionType("audio", path) return isExtensionType("audio", path)
} }
fun isPDF(path: String): Boolean { fun isPDF(path: String): Boolean {
return isExtensionType("pdf", path) return isExtensionType("pdf", path)
} }
fun isText(path: String): Boolean { fun isText(path: String): Boolean {
return isExtensionType("text", path) return isExtensionType("text", path)
}
} }
} }

View File

@ -30,13 +30,14 @@ class GocryptfsVolume(val applicationContext: Context, var sessionID: Int) {
const val KeyLen = 32 const val KeyLen = 32
const val ScryptDefaultLogN = 16 const val ScryptDefaultLogN = 16
const val DefaultBS = 4096 const val DefaultBS = 4096
const val CONFIG_FILE_NAME = "gocryptfs.conf"
external fun createVolume(root_cipher_dir: String, password: CharArray, plainTextNames: Boolean, xchacha: Int, logN: Int, creator: String, returnedHash: ByteArray?): Boolean external fun createVolume(root_cipher_dir: String, password: CharArray, plainTextNames: Boolean, xchacha: Int, logN: Int, creator: String, returnedHash: ByteArray?): Boolean
external fun init(root_cipher_dir: String, password: CharArray?, givenHash: ByteArray?, returnedHash: ByteArray?): Int external fun init(root_cipher_dir: String, password: CharArray?, givenHash: ByteArray?, returnedHash: ByteArray?): Int
external fun changePassword(root_cipher_dir: String, old_password: CharArray?, givenHash: ByteArray?, new_password: CharArray, returnedHash: ByteArray?): Boolean external fun changePassword(root_cipher_dir: String, old_password: CharArray?, givenHash: ByteArray?, new_password: CharArray, returnedHash: ByteArray?): Boolean
fun isGocryptfsVolume(path: File): Boolean { fun isGocryptfsVolume(path: File): Boolean {
if (path.isDirectory){ if (path.isDirectory){
return File(path, ConstValues.gocryptfsConfFilename).isFile return File(path, CONFIG_FILE_NAME).isFile
} }
return false return false
} }

View File

@ -1,13 +1,20 @@
package sushi.hardcore.droidfs package sushi.hardcore.droidfs
import android.content.SharedPreferences
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
import android.view.inputmethod.EditorInfo
import android.widget.Toast
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference import androidx.preference.SwitchPreference
import sushi.hardcore.droidfs.databinding.ActivitySettingsBinding import sushi.hardcore.droidfs.databinding.ActivitySettingsBinding
import sushi.hardcore.droidfs.databinding.DialogEditTextBinding
import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
import java.lang.NumberFormatException
class SettingsActivity : BaseActivity() { class SettingsActivity : BaseActivity() {
@ -20,7 +27,7 @@ class SettingsActivity : BaseActivity() {
val fragment = if (screen == "UnsafeFeaturesSettingsFragment") { val fragment = if (screen == "UnsafeFeaturesSettingsFragment") {
UnsafeFeaturesSettingsFragment() UnsafeFeaturesSettingsFragment()
} else { } else {
MainSettingsFragment() MainSettingsFragment(sharedPrefs)
} }
supportFragmentManager supportFragmentManager
.beginTransaction() .beginTransaction()
@ -38,13 +45,71 @@ class SettingsActivity : BaseActivity() {
} }
} }
class MainSettingsFragment : PreferenceFragmentCompat() { class MainSettingsFragment(private val sharedPrefs: SharedPreferences) : PreferenceFragmentCompat() {
private lateinit var maxSizePreference: Preference
private fun setThumbnailMaxSize(input: String) {
val value: Long
try {
value = input.toLong()
} catch (e: NumberFormatException) {
Toast.makeText(requireContext(), R.string.invalid_number, Toast.LENGTH_SHORT).show()
showMaxSizeDialog()
return
}
val size = value*1000
if (size < 0) {
Toast.makeText(requireContext(), R.string.invalid_number, Toast.LENGTH_SHORT).show()
showMaxSizeDialog()
} else {
with(sharedPrefs.edit()) {
putLong(ConstValues.THUMBNAIL_MAX_SIZE_KEY, value)
apply()
}
maxSizePreference.summary = PathUtils.formatSize(size)
}
}
private fun showMaxSizeDialog() {
val dialogBinding = DialogEditTextBinding.inflate(layoutInflater)
with (dialogBinding.dialogEditText) {
inputType = EditorInfo.TYPE_CLASS_NUMBER
hint = getString(R.string.size_hint)
}
val dialog = CustomAlertDialogBuilder(requireContext(), (requireActivity() as BaseActivity).themeValue)
.setTitle(R.string.thumbnail_max_size)
.setView(dialogBinding.root)
.setPositiveButton(R.string.ok) { _, _ ->
setThumbnailMaxSize(dialogBinding.dialogEditText.text.toString())
}
.create()
dialogBinding.dialogEditText.setOnEditorActionListener { _, _, _ ->
dialog.dismiss()
setThumbnailMaxSize(dialogBinding.dialogEditText.text.toString())
true
}
dialog.show()
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey) setPreferencesFromResource(R.xml.root_preferences, rootKey)
findPreference<ListPreference>("theme")?.setOnPreferenceChangeListener { _, newValue -> findPreference<ListPreference>("theme")?.setOnPreferenceChangeListener { _, newValue ->
(activity as BaseActivity).onThemeChanged(newValue as String) (activity as BaseActivity).onThemeChanged(newValue as String)
true true
} }
findPreference<Preference>(ConstValues.THUMBNAIL_MAX_SIZE_KEY)?.let {
maxSizePreference = it
maxSizePreference.summary = getString(
R.string.thumbnail_max_size_summary,
PathUtils.formatSize(sharedPrefs.getLong(
ConstValues.THUMBNAIL_MAX_SIZE_KEY, ConstValues.DEFAULT_THUMBNAIL_MAX_SIZE
)*1000)
)
maxSizePreference.setOnPreferenceClickListener {
showMaxSizeDialog()
false
}
}
} }
} }

View File

@ -6,7 +6,7 @@ import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper import android.database.sqlite.SQLiteOpenHelper
class VolumeDatabase(context: Context): SQLiteOpenHelper(context, class VolumeDatabase(context: Context): SQLiteOpenHelper(context,
ConstValues.volumeDatabaseName, null, 3) { ConstValues.VOLUME_DATABASE_NAME, null, 3) {
companion object { companion object {
const val TABLE_NAME = "Volumes" const val TABLE_NAME = "Volumes"
const val COLUMN_NAME = "name" const val COLUMN_NAME = "name"

View File

@ -23,7 +23,8 @@ class ExplorerElementAdapter(
val activity: AppCompatActivity, val activity: AppCompatActivity,
val gocryptfsVolume: GocryptfsVolume?, val gocryptfsVolume: GocryptfsVolume?,
val onExplorerElementClick: (Int) -> Unit, val onExplorerElementClick: (Int) -> Unit,
val onExplorerElementLongClick: (Int) -> Unit val onExplorerElementLongClick: (Int) -> Unit,
val thumbnailMaxSize: Long,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
val dateFormat: DateFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.getDefault()) val dateFormat: DateFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.getDefault())
var explorerElements = listOf<ExplorerElement>() var explorerElements = listOf<ExplorerElement>()
@ -127,7 +128,7 @@ class ExplorerElementAdapter(
adapter.gocryptfsVolume?.let { volume -> adapter.gocryptfsVolume?.let { volume ->
displayThumbnail = true displayThumbnail = true
Thread { Thread {
volume.loadWholeFile(fullPath, maxSize = 50_000_000).first?.let { volume.loadWholeFile(fullPath, maxSize = adapter.thumbnailMaxSize).first?.let {
if (displayThumbnail) { if (displayThumbnail) {
adapter.activity.runOnUiThread { adapter.activity.runOnUiThread {
if (displayThumbnail) { if (displayThumbnail) {

View File

@ -127,7 +127,7 @@ class CreateVolumeFragment: Fragment() {
var returnedHash: ByteArray? = null var returnedHash: ByteArray? = null
if (binding.checkboxSavePassword.isChecked) if (binding.checkboxSavePassword.isChecked)
returnedHash = ByteArray(GocryptfsVolume.KeyLen) returnedHash = ByteArray(GocryptfsVolume.KeyLen)
if (GocryptfsVolume.createVolume(volumePath, password, false, xchacha, GocryptfsVolume.ScryptDefaultLogN, ConstValues.creator, returnedHash)) { if (GocryptfsVolume.createVolume(volumePath, password, false, xchacha, GocryptfsVolume.ScryptDefaultLogN, ConstValues.CREATOR, returnedHash)) {
val volumeName = if (isHiddenVolume) File(volumePath).name else volumePath val volumeName = if (isHiddenVolume) File(volumePath).name else volumePath
val volume = Volume(volumeName, isHiddenVolume) val volume = Volume(volumeName, isHiddenVolume)
volumeDatabase.apply { volumeDatabase.apply {

View File

@ -23,11 +23,11 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import sushi.hardcore.droidfs.BaseActivity import sushi.hardcore.droidfs.BaseActivity
import sushi.hardcore.droidfs.ConstValues import sushi.hardcore.droidfs.ConstValues
import sushi.hardcore.droidfs.ConstValues.Companion.isAudio import sushi.hardcore.droidfs.ConstValues.isAudio
import sushi.hardcore.droidfs.ConstValues.Companion.isImage import sushi.hardcore.droidfs.ConstValues.isImage
import sushi.hardcore.droidfs.ConstValues.Companion.isPDF import sushi.hardcore.droidfs.ConstValues.isPDF
import sushi.hardcore.droidfs.ConstValues.Companion.isText import sushi.hardcore.droidfs.ConstValues.isText
import sushi.hardcore.droidfs.ConstValues.Companion.isVideo import sushi.hardcore.droidfs.ConstValues.isVideo
import sushi.hardcore.droidfs.GocryptfsVolume import sushi.hardcore.droidfs.GocryptfsVolume
import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.adapters.ExplorerElementAdapter import sushi.hardcore.droidfs.adapters.ExplorerElementAdapter
@ -79,7 +79,7 @@ open class BaseExplorerActivity : BaseActivity() {
sortOrderValues = resources.getStringArray(R.array.sort_orders_values) sortOrderValues = resources.getStringArray(R.array.sort_orders_values)
foldersFirst = sharedPrefs.getBoolean("folders_first", true) foldersFirst = sharedPrefs.getBoolean("folders_first", true)
mapFolders = sharedPrefs.getBoolean("map_folders", true) mapFolders = sharedPrefs.getBoolean("map_folders", true)
currentSortOrderIndex = resources.getStringArray(R.array.sort_orders_values).indexOf(sharedPrefs.getString(ConstValues.sort_order_key, "name")) currentSortOrderIndex = resources.getStringArray(R.array.sort_orders_values).indexOf(sharedPrefs.getString(ConstValues.SORT_ORDER_KEY, "name"))
init() init()
recycler_view_explorer = findViewById(R.id.recycler_view_explorer) recycler_view_explorer = findViewById(R.id.recycler_view_explorer)
refresher = findViewById(R.id.refresher) refresher = findViewById(R.id.refresher)
@ -101,7 +101,8 @@ open class BaseExplorerActivity : BaseActivity() {
null null
}, },
::onExplorerItemClick, ::onExplorerItemClick,
::onExplorerItemLongClick ::onExplorerItemLongClick,
sharedPrefs.getLong(ConstValues.THUMBNAIL_MAX_SIZE_KEY, ConstValues.DEFAULT_THUMBNAIL_MAX_SIZE)*1000,
) )
explorerViewModel= ViewModelProvider(this).get(ExplorerViewModel::class.java) explorerViewModel= ViewModelProvider(this).get(ExplorerViewModel::class.java)
currentDirectoryPath = explorerViewModel.currentDirectoryPath currentDirectoryPath = explorerViewModel.currentDirectoryPath
@ -224,7 +225,7 @@ open class BaseExplorerActivity : BaseActivity() {
explorerAdapter.explorerElements = explorerElements explorerAdapter.explorerElements = explorerElements
unselectAll() unselectAll()
val sharedPrefsEditor = sharedPrefs.edit() val sharedPrefsEditor = sharedPrefs.edit()
sharedPrefsEditor.putString(ConstValues.sort_order_key, sortOrderValues[currentSortOrderIndex]) sharedPrefsEditor.putString(ConstValues.SORT_ORDER_KEY, sortOrderValues[currentSortOrderIndex])
sharedPrefsEditor.apply() sharedPrefsEditor.apply()
} }

View File

@ -21,7 +21,7 @@ class GocryptfsDataSource(private val gocryptfsVolume: GocryptfsVolume, private
} }
override fun getUri(): Uri { override fun getUri(): Uri {
return ConstValues.fakeUri return ConstValues.FAKE_URI
} }
override fun close() { override fun close() {

View File

@ -121,7 +121,7 @@ class ImageViewer: FileViewerActivity() {
binding.imageButtonSlideshow.setOnClickListener { binding.imageButtonSlideshow.setOnClickListener {
if (!slideshowActive){ if (!slideshowActive){
slideshowActive = true slideshowActive = true
handler.postDelayed(slideshowNext, ConstValues.slideshow_delay) handler.postDelayed(slideshowNext, ConstValues.SLIDESHOW_DELAY)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
hideUI.run() hideUI.run()
Toast.makeText(this, R.string.slideshow_started, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.slideshow_started, Toast.LENGTH_SHORT).show()
@ -204,7 +204,7 @@ class ImageViewer: FileViewerActivity() {
if (!slideshowSwipe) { //reset slideshow delay if user swipes if (!slideshowSwipe) { //reset slideshow delay if user swipes
handler.removeCallbacks(slideshowNext) handler.removeCallbacks(slideshowNext)
} }
handler.postDelayed(slideshowNext, ConstValues.slideshow_delay) handler.postDelayed(slideshowNext, ConstValues.SLIDESHOW_DELAY)
} }
} }

View File

@ -27,7 +27,7 @@ abstract class MediaPlayer: FileViewerActivity() {
private fun createMediaSource(filePath: String): MediaSource { private fun createMediaSource(filePath: String): MediaSource {
val dataSourceFactory = GocryptfsDataSource.Factory(gocryptfsVolume, filePath) val dataSourceFactory = GocryptfsDataSource.Factory(gocryptfsVolume, filePath)
return ProgressiveMediaSource.Factory(dataSourceFactory, DefaultExtractorsFactory()) return ProgressiveMediaSource.Factory(dataSourceFactory, DefaultExtractorsFactory())
.createMediaSource(MediaItem.fromUri(ConstValues.fakeUri)) .createMediaSource(MediaItem.fromUri(ConstValues.FAKE_URI))
} }
private fun initializePlayer(){ private fun initializePlayer(){

View File

@ -88,7 +88,7 @@ object PathUtils {
return result return result
} }
private val units = arrayOf("B", "kB", "MB", "GB", "TB") private val units = arrayOf("B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
fun formatSize(size: Long): String { fun formatSize(size: Long): String {
if (size <= 0) { if (size <= 0) {
return "0 B" return "0 B"

View File

@ -25,11 +25,11 @@ object Wiper {
val buff = ByteArray(buff_size) val buff = ByteArray(buff_size)
Arrays.fill(buff, 0.toByte()) Arrays.fill(buff, 0.toByte())
val writes = ceil(size.toDouble() / buff_size).toInt() val writes = ceil(size.toDouble() / buff_size).toInt()
for (i in 0 until ConstValues.wipe_passes) { for (i in 0 until ConstValues.WIPE_PASSES) {
for (j in 0 until writes) { for (j in 0 until writes) {
os.write(buff) os.write(buff)
} }
if (i < ConstValues.wipe_passes - 1) { if (i < ConstValues.WIPE_PASSES - 1) {
//reopening to flush and seek //reopening to flush and seek
os.close() os.close()
os = context.contentResolver.openOutputStream(uri)!! os = context.contentResolver.openOutputStream(uri)!!
@ -57,11 +57,11 @@ object Wiper {
val buff = ByteArray(buff_size) val buff = ByteArray(buff_size)
Arrays.fill(buff, 0.toByte()) Arrays.fill(buff, 0.toByte())
val writes = ceil(size.toDouble() / buff_size).toInt() val writes = ceil(size.toDouble() / buff_size).toInt()
for (i in 0 until ConstValues.wipe_passes) { for (i in 0 until ConstValues.WIPE_PASSES) {
for (j in 0 until writes) { for (j in 0 until writes) {
os.write(buff) os.write(buff)
} }
if (i < ConstValues.wipe_passes - 1) { if (i < ConstValues.WIPE_PASSES - 1) {
//reopening to flush and seek //reopening to flush and seek
os.close() os.close()
os = FileOutputStream(file) os = FileOutputStream(file)

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorAccent"
android:pathData="M21.9,21.9l-8.49,-8.49l0,0L3.59,3.59l0,0L2.1,2.1L0.69,3.51L3,5.83V19c0,1.1 0.9,2 2,2h13.17l2.31,2.31L21.9,21.9zM5,18l3.5,-4.5l2.5,3.01L12.17,15l3,3H5zM21,18.17L5.83,3H19c1.1,0 2,0.9 2,2V18.17z"/>
</vector>

View File

@ -228,4 +228,8 @@
<string name="copy_volume_notification">Copying volume…</string> <string name="copy_volume_notification">Copying volume…</string>
<string name="hidden_volume_already_exists">A hidden volume with the same name already exists.</string> <string name="hidden_volume_already_exists">A hidden volume with the same name already exists.</string>
<string name="pdf_document">PDF document</string> <string name="pdf_document">PDF document</string>
<string name="thumbnail_max_size">Maximum size for thumbnails</string>
<string name="thumbnail_max_size_summary">Maximum file size for which to load a thumbnail. Current value: %s</string>
<string name="size_hint">Size (in KB)</string>
<string name="invalid_number">Invalid number</string>
</resources> </resources>

View File

@ -52,6 +52,11 @@
android:title="@string/thumbnails" android:title="@string/thumbnails"
android:summary="@string/thumbnails_summary"/> android:summary="@string/thumbnails_summary"/>
<Preference
android:icon="@drawable/icon_image_crossed_out"
android:key="thumbnail_max_size"
android:title="@string/thumbnail_max_size"/>
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:defaultValue="true" android:defaultValue="true"
app:icon="@drawable/icon_folder_search" app:icon="@drawable/icon_folder_search"