libcryfs genesis

This commit is contained in:
Matéo Duparc 2022-06-18 21:13:16 +02:00
parent 83dd759f36
commit dbd04848bd
38 changed files with 973 additions and 499 deletions

View File

@ -1,5 +1,7 @@
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.10)
add_subdirectory(${PROJECT_SOURCE_DIR}/libcryfs/)
add_library( add_library(
gocryptfs gocryptfs
SHARED SHARED
@ -23,6 +25,10 @@ target_link_libraries(
gocryptfs gocryptfs
) )
add_library(cryfs_jni SHARED src/main/native/libcryfs.c)
#file(GLOB CRYFS_STATIC_LIBRARIES ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/lib*.a)
target_link_libraries(cryfs_jni fspp-fuse)#${CRYFS_STATIC_LIBRARIES})
add_library( add_library(
avformat avformat
SHARED SHARED

View File

@ -4,7 +4,7 @@ apply plugin: 'kotlin-android'
android { android {
compileSdkVersion 31 compileSdkVersion 31
buildToolsVersion "31" buildToolsVersion "31"
ndkVersion "23.1.7779620" ndkVersion "24.0.8215888"
compileOptions { compileOptions {
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8

View File

@ -30,6 +30,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
import sushi.hardcore.droidfs.databinding.ActivityCameraBinding import sushi.hardcore.droidfs.databinding.ActivityCameraBinding
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.video_recording.SeekableWriter import sushi.hardcore.droidfs.video_recording.SeekableWriter
import sushi.hardcore.droidfs.video_recording.VideoCapture import sushi.hardcore.droidfs.video_recording.VideoCapture
@ -64,7 +65,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
private lateinit var sensorOrientationListener: SensorOrientationListener private lateinit var sensorOrientationListener: SensorOrientationListener
private var previousOrientation: Float = 0f private var previousOrientation: Float = 0f
private lateinit var orientedIcons: List<ImageView> private lateinit var orientedIcons: List<ImageView>
private lateinit var gocryptfsVolume: GocryptfsVolume private lateinit var encryptedVolume: EncryptedVolume
private lateinit var outputDirectory: String private lateinit var outputDirectory: String
private var isFinishingIntentionally = false private var isFinishingIntentionally = false
private var isAskingPermissions = false private var isAskingPermissions = false
@ -93,7 +94,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
binding = ActivityCameraBinding.inflate(layoutInflater) binding = ActivityCameraBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
supportActionBar?.hide() supportActionBar?.hide()
gocryptfsVolume = GocryptfsVolume(applicationContext, intent.getIntExtra("sessionID", -1)) encryptedVolume = intent.getParcelableExtra("volume")!!
outputDirectory = intent.getStringExtra("path")!! outputDirectory = intent.getStringExtra("path")!!
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@ -381,7 +382,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
var fileName: String var fileName: String
do { do {
fileName = baseName+(random.nextInt(fileNameRandomMax-fileNameRandomMin)+fileNameRandomMin)+'.'+ if (isVideo) {"mp4"} else {"jpg"} fileName = baseName+(random.nextInt(fileNameRandomMax-fileNameRandomMin)+fileNameRandomMin)+'.'+ if (isVideo) {"mp4"} else {"jpg"}
} while (gocryptfsVolume.pathExists(fileName)) } while (encryptedVolume.pathExists(fileName))
return PathUtils.pathJoin(outputDirectory, fileName) return PathUtils.pathJoin(outputDirectory, fileName)
} }
@ -415,7 +416,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
imageCapture.takePicture(outputOptions, executor, object : ImageCapture.OnImageSavedCallback { imageCapture.takePicture(outputOptions, executor, object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
binding.takePhotoButton.onPhotoTaken() binding.takePhotoButton.onPhotoTaken()
if (gocryptfsVolume.importFile(ByteArrayInputStream(outputBuff.toByteArray()), outputPath)) { if (encryptedVolume.importFile(ByteArrayInputStream(outputBuff.toByteArray()), outputPath)) {
Toast.makeText(applicationContext, getString(R.string.picture_save_success, outputPath), Toast.LENGTH_SHORT).show() Toast.makeText(applicationContext, getString(R.string.picture_save_success, outputPath), Toast.LENGTH_SHORT).show()
} else { } else {
CustomAlertDialogBuilder(this@CameraActivity, themeValue) CustomAlertDialogBuilder(this@CameraActivity, themeValue)
@ -446,18 +447,18 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
isRecording = false isRecording = false
} else if (!isWaitingForTimer) { } else if (!isWaitingForTimer) {
val path = getOutputPath(true) val path = getOutputPath(true)
startTimerThen { /*startTimerThen {
val handleId = gocryptfsVolume.openWriteMode(path) val handleId = encryptedVolume.openWriteMode(path)
videoCapture?.startRecording(VideoCapture.OutputFileOptions(object : SeekableWriter { videoCapture?.startRecording(VideoCapture.OutputFileOptions(object : SeekableWriter {
var offset = 0L var offset = 0L
override fun write(byteArray: ByteArray) { override fun write(byteArray: ByteArray) {
offset += gocryptfsVolume.writeFile(handleId, offset, byteArray, byteArray.size) offset += encryptedVolume.writeFile(handleId, offset, byteArray, byteArray.size)
} }
override fun seek(offset: Long) { override fun seek(offset: Long) {
this.offset = offset this.offset = offset
} }
override fun close() { override fun close() {
gocryptfsVolume.closeFile(handleId) encryptedVolume.closeFile(handleId)
} }
}), executor, object : VideoCapture.OnVideoSavedCallback { }), executor, object : VideoCapture.OnVideoSavedCallback {
override fun onVideoSaved() { override fun onVideoSaved() {
@ -472,14 +473,14 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
}) })
binding.recordVideoButton.setImageResource(R.drawable.stop_recording_video_button) binding.recordVideoButton.setImageResource(R.drawable.stop_recording_video_button)
isRecording = true isRecording = true
} }*/
} }
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
if (!isFinishingIntentionally) { if (!isFinishingIntentionally) {
gocryptfsVolume.close() encryptedVolume.close()
RestrictedFileProvider.wipeAll(this) RestrictedFileProvider.wipeAll(this)
} }
} }

View File

@ -15,7 +15,7 @@ import java.util.*
class ChangePasswordActivity: BaseActivity() { class ChangePasswordActivity: BaseActivity() {
private lateinit var binding: ActivityChangePasswordBinding private lateinit var binding: ActivityChangePasswordBinding
private lateinit var volume: Volume private lateinit var volume: SavedVolume
private lateinit var volumeDatabase: VolumeDatabase private lateinit var volumeDatabase: VolumeDatabase
private var fingerprintProtector: FingerprintProtector? = null private var fingerprintProtector: FingerprintProtector? = null
private var usfFingerprint: Boolean = false private var usfFingerprint: Boolean = false

View File

@ -5,8 +5,6 @@ import java.io.File
object ConstValues { object ConstValues {
const val CREATOR = "DroidFS" const val CREATOR = "DroidFS"
const val FILE_MODE = 384 //0600
const val DIRECTORY_MODE = 448 //0700
const val VOLUME_DATABASE_NAME = "SavedVolumes" const val VOLUME_DATABASE_NAME = "SavedVolumes"
const val SORT_ORDER_KEY = "sort_order" const val SORT_ORDER_KEY = "sort_order"
val FAKE_URI: Uri = Uri.parse("fakeuri://droidfs") val FAKE_URI: Uri = Uri.parse("fakeuri://droidfs")

View File

@ -133,7 +133,7 @@ class FingerprintProtector private constructor(
private lateinit var cipher: Cipher private lateinit var cipher: Cipher
private var isCipherReady = false private var isCipherReady = false
private var cipherActionMode: Int? = null private var cipherActionMode: Int? = null
private lateinit var volume: Volume private lateinit var volume: SavedVolume
private lateinit var dataToProcess: ByteArray private lateinit var dataToProcess: ByteArray
private fun resetHashStorage() { private fun resetHashStorage() {
@ -207,7 +207,7 @@ class FingerprintProtector private constructor(
.show() .show()
} }
fun savePasswordHash(volume: Volume, plainText: ByteArray) { fun savePasswordHash(volume: SavedVolume, plainText: ByteArray) {
this.volume = volume this.volume = volume
val biometricPromptInfo = BiometricPrompt.PromptInfo.Builder() val biometricPromptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle(activity.getString(R.string.encrypt_action_description)) .setTitle(activity.getString(R.string.encrypt_action_description))

View File

@ -1,15 +1,11 @@
package sushi.hardcore.droidfs package sushi.hardcore.droidfs
import android.content.Context import android.os.Parcel
import android.net.Uri
import sushi.hardcore.droidfs.explorers.ExplorerElement import sushi.hardcore.droidfs.explorers.ExplorerElement
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import java.io.File import sushi.hardcore.droidfs.filesystems.Stat
import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
class GocryptfsVolume(val applicationContext: Context, var sessionID: Int) { class GocryptfsVolume(private val sessionID: Int): EncryptedVolume() {
private external fun native_close(sessionID: Int) private external fun native_close(sessionID: Int)
private external fun native_is_closed(sessionID: Int): Boolean private external fun native_is_closed(sessionID: Int): Boolean
private external fun native_list_dir(sessionID: Int, dir_path: String): MutableList<ExplorerElement> private external fun native_list_dir(sessionID: Int, dir_path: String): MutableList<ExplorerElement>
@ -17,13 +13,12 @@ class GocryptfsVolume(val applicationContext: Context, var sessionID: Int) {
private external fun native_open_write_mode(sessionID: Int, file_path: String, mode: Int): Int private external fun native_open_write_mode(sessionID: Int, file_path: String, mode: Int): Int
private external fun native_read_file(sessionID: Int, handleID: Int, offset: Long, buff: ByteArray): Int private external fun native_read_file(sessionID: Int, handleID: Int, offset: Long, buff: ByteArray): Int
private external fun native_write_file(sessionID: Int, handleID: Int, offset: Long, buff: ByteArray, buff_size: Int): Int private external fun native_write_file(sessionID: Int, handleID: Int, offset: Long, buff: ByteArray, buff_size: Int): Int
private external fun native_truncate(sessionID: Int, handleID: Int, offset: Long): Boolean private external fun native_truncate(sessionID: Int, path: String, offset: Long): Boolean
private external fun native_path_exists(sessionID: Int, file_path: String): Boolean
private external fun native_get_size(sessionID: Int, file_path: String): Long
private external fun native_close_file(sessionID: Int, handleID: Int) private external fun native_close_file(sessionID: Int, handleID: Int)
private external fun native_remove_file(sessionID: Int, file_path: String): Boolean private external fun native_remove_file(sessionID: Int, file_path: String): Boolean
private external fun native_mkdir(sessionID: Int, dir_path: String, mode: Int): Boolean private external fun native_mkdir(sessionID: Int, dir_path: String, mode: Int): Boolean
private external fun native_rmdir(sessionID: Int, dir_path: String): Boolean private external fun native_rmdir(sessionID: Int, dir_path: String): Boolean
private external fun native_get_attr(sessionID: Int, file_path: String): Stat?
private external fun native_rename(sessionID: Int, old_path: String, new_path: String): Boolean private external fun native_rename(sessionID: Int, old_path: String, new_path: String): Boolean
companion object { companion object {
@ -32,14 +27,16 @@ class GocryptfsVolume(val applicationContext: Context, var sessionID: Int) {
const val DefaultBS = 4096 const val DefaultBS = 4096
const val CONFIG_FILE_NAME = "gocryptfs.conf" 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 nativeInit(root_cipher_dir: String, password: ByteArray?, 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 init(root_cipher_dir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ByteArray?): GocryptfsVolume? {
if (path.isDirectory){ val sessionId = nativeInit(root_cipher_dir, password, givenHash, returnedHash)
return File(path, CONFIG_FILE_NAME).isFile return if (sessionId == -1) {
null
} else {
GocryptfsVolume(sessionId)
} }
return false
} }
init { init {
@ -47,197 +44,63 @@ class GocryptfsVolume(val applicationContext: Context, var sessionID: Int) {
} }
} }
fun close() { constructor(parcel: Parcel) : this(parcel.readInt())
override fun openFile(path: String): Long {
return native_open_write_mode(sessionID, path, 0).toLong()
}
override fun read(fileHandle: Long, buffer: ByteArray, offset: Long): Int {
return native_read_file(sessionID, fileHandle.toInt(), offset, buffer)
}
override fun readDir(path: String): MutableList<ExplorerElement>? {
return native_list_dir(sessionID, path)
}
override fun getAttr(path: String): Stat? {
return native_get_attr(sessionID, path)
}
override fun writeToParcel(parcel: Parcel, flags: Int) = with(parcel) {
writeByte(GOCRYPTFS_VOLUME_TYPE)
writeInt(sessionID)
}
override fun close() {
native_close(sessionID) native_close(sessionID)
} }
fun isClosed(): Boolean { override fun isClosed(): Boolean {
return native_is_closed(sessionID) return native_is_closed(sessionID)
} }
fun listDir(dir_path: String): MutableList<ExplorerElement> { override fun mkdir(path: String): Boolean {
return native_list_dir(sessionID, dir_path) return native_mkdir(sessionID, path, 0)
} }
fun mkdir(dir_path: String): Boolean { override fun rmdir(path: String): Boolean {
return native_mkdir(sessionID, dir_path, ConstValues.DIRECTORY_MODE) return native_rmdir(sessionID, path)
} }
fun rmdir(dir_path: String): Boolean { override fun closeFile(fileHandle: Long): Boolean {
return native_rmdir(sessionID, dir_path) native_close_file(sessionID, fileHandle.toInt())
}
fun removeFile(file_path: String): Boolean {
return native_remove_file(sessionID, file_path)
}
fun pathExists(file_path: String): Boolean {
return native_path_exists(sessionID, file_path)
}
fun getSize(file_path: String): Long {
return native_get_size(sessionID, file_path)
}
fun closeFile(handleID: Int) {
native_close_file(sessionID, handleID)
}
fun openReadMode(file_path: String): Int {
return native_open_read_mode(sessionID, file_path)
}
fun openWriteMode(file_path: String): Int {
return native_open_write_mode(sessionID, file_path, ConstValues.FILE_MODE)
}
fun readFile(handleID: Int, offset: Long, buff: ByteArray): Int {
return native_read_file(sessionID, handleID, offset, buff)
}
fun writeFile(handleID: Int, offset: Long, buff: ByteArray, buff_size: Int): Int {
return native_write_file(sessionID, handleID, offset, buff, buff_size)
}
fun truncate(handleID: Int, offset: Long): Boolean {
return native_truncate(sessionID, handleID, offset)
}
fun rename(old_path: String, new_path: String): Boolean {
return native_rename(sessionID, old_path, new_path)
}
fun exportFile(handleID: Int, os: OutputStream): Boolean {
var offset: Long = 0
val ioBuffer = ByteArray(DefaultBS)
var length: Int
while (readFile(handleID, offset, ioBuffer).also { length = it } > 0){
os.write(ioBuffer, 0, length)
offset += length.toLong()
}
os.close()
return true return true
} }
fun exportFile(src_path: String, os: OutputStream): Boolean { override fun write(fileHandle: Long, offset: Long, buffer: ByteArray, size: Int): Int {
var success = false return native_write_file(sessionID, fileHandle.toInt(), offset, buffer, size)
val srcHandleId = openReadMode(src_path)
if (srcHandleId != -1) {
success = exportFile(srcHandleId, os)
closeFile(srcHandleId)
}
return success
} }
fun exportFile(src_path: String, dst_path: String): Boolean { override fun truncate(path: String, size: Long): Boolean {
return exportFile(src_path, FileOutputStream(dst_path)) return native_truncate(sessionID, path, size)
} }
fun exportFile(context: Context, src_path: String, output_path: Uri): Boolean { override fun deleteFile(path: String): Boolean {
val os = context.contentResolver.openOutputStream(output_path) return native_remove_file(sessionID, path)
if (os != null){
return exportFile(src_path, os)
}
return false
} }
fun importFile(inputStream: InputStream, dst_path: String): Boolean { override fun rename(srcPath: String, dstPath: String): Boolean {
val dstHandleId = openWriteMode(dst_path) return native_rename(sessionID, srcPath, dstPath)
if (dstHandleId != -1) {
var success = true
var offset: Long = 0
val ioBuffer = ByteArray(DefaultBS)
var length: Int
while (inputStream.read(ioBuffer).also { length = it } > 0) {
val written = writeFile(dstHandleId, offset, ioBuffer, length).toLong()
if (written == length.toLong()) {
offset += written
} else {
inputStream.close()
success = false
break
}
}
closeFile(dstHandleId)
inputStream.close()
return success
}
return false
}
fun importFile(context: Context, src_uri: Uri, dst_path: String): Boolean {
val inputStream = context.contentResolver.openInputStream(src_uri)
if (inputStream != null){
return importFile(inputStream, dst_path)
}
return false
}
fun recursiveMapFiles(rootPath: String): MutableList<ExplorerElement> {
val result = mutableListOf<ExplorerElement>()
val explorerElements = listDir(rootPath)
result.addAll(explorerElements)
for (e in explorerElements){
if (e.isDirectory){
result.addAll(recursiveMapFiles(e.fullPath))
}
}
return result
}
fun recursiveRemoveDirectory(plain_directory_path: String): String? {
val explorerElements = listDir(plain_directory_path)
for (e in explorerElements) {
val fullPath = PathUtils.pathJoin(plain_directory_path, e.name)
if (e.isDirectory) {
val result = recursiveRemoveDirectory(fullPath)
result?.let { return it }
} else {
if (!removeFile(fullPath)) {
return fullPath
}
}
}
return if (!rmdir(plain_directory_path)) {
plain_directory_path
} else {
null
}
}
fun loadWholeFile(fullPath: String, size: Long? = null, maxSize: Long? = null): Pair<ByteArray?, Int> {
val fileSize = size ?: getSize(fullPath)
return if (fileSize >= 0) {
maxSize?.let {
if (fileSize > it) {
return Pair(null, 0)
}
}
try {
val fileBuff = ByteArray(fileSize.toInt())
val handleID = openReadMode(fullPath)
if (handleID == -1) {
Pair(null, 3)
} else {
var offset: Long = 0
val ioBuffer = ByteArray(DefaultBS)
var length: Int
while (readFile(handleID, offset, ioBuffer).also { length = it } > 0) {
System.arraycopy(ioBuffer, 0, fileBuff, offset.toInt(), length)
offset += length.toLong()
}
closeFile(handleID)
if (offset == fileBuff.size.toLong()) {
Pair(fileBuff, 0)
} else {
Pair(null, 4)
}
}
} catch (e: OutOfMemoryError) {
Pair(null, 2)
}
} else {
Pair(null, 1)
}
} }
} }

View File

@ -30,10 +30,13 @@ import sushi.hardcore.droidfs.explorers.ExplorerActivity
import sushi.hardcore.droidfs.explorers.ExplorerActivityDrop import sushi.hardcore.droidfs.explorers.ExplorerActivityDrop
import sushi.hardcore.droidfs.explorers.ExplorerActivityPick import sushi.hardcore.droidfs.explorers.ExplorerActivityPick
import sushi.hardcore.droidfs.file_operations.FileOperationService import sushi.hardcore.droidfs.file_operations.FileOperationService
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
import sushi.hardcore.droidfs.widgets.EditTextDialog import sushi.hardcore.droidfs.widgets.EditTextDialog
import java.io.File import java.io.File
import java.nio.CharBuffer
import java.nio.charset.StandardCharsets
import java.util.* import java.util.*
class MainActivity : BaseActivity(), VolumeAdapter.Listener { class MainActivity : BaseActivity(), VolumeAdapter.Listener {
@ -155,7 +158,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
} }
} }
override fun onVolumeItemClick(volume: Volume, position: Int) { override fun onVolumeItemClick(volume: SavedVolume, position: Int) {
if (volumeAdapter.selectedItems.isEmpty()) if (volumeAdapter.selectedItems.isEmpty())
openVolume(volume, position) openVolume(volume, position)
else else
@ -186,7 +189,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
invalidateOptionsMenu() invalidateOptionsMenu()
} }
private fun removeVolumes(volumes: List<Volume>, i: Int = 0, doDeleteVolumeContent: Boolean? = null) { private fun removeVolumes(volumes: List<SavedVolume>, i: Int = 0, doDeleteVolumeContent: Boolean? = null) {
if (i < volumes.size) { if (i < volumes.size) {
if (volumes[i].isHidden) { if (volumes[i].isHidden) {
if (doDeleteVolumeContent == null) { if (doDeleteVolumeContent == null) {
@ -306,7 +309,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
DocumentFile.fromFile(File(volume.name)), DocumentFile.fromFile(File(volume.name)),
DocumentFile.fromFile(filesDir), DocumentFile.fromFile(filesDir),
) { ) {
Volume(volume.shortName, true, volume.encryptedHash, volume.iv) SavedVolume(volume.shortName, true, volume.type, volume.encryptedHash, volume.iv)
} }
} }
} }
@ -377,9 +380,10 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
dstRootDirectory.name?.let { name -> dstRootDirectory.name?.let { name ->
val path = PathUtils.getFullPathFromTreeUri(dstRootDirectory.uri, this) val path = PathUtils.getFullPathFromTreeUri(dstRootDirectory.uri, this)
if (path == null) null if (path == null) null
else Volume( else SavedVolume(
PathUtils.pathJoin(path, name), PathUtils.pathJoin(path, name),
false, false,
volume.type,
volume.encryptedHash, volume.encryptedHash,
volume.iv volume.iv
) )
@ -388,7 +392,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
} }
} }
private fun copyVolume(srcDocumentFile: DocumentFile, dstDocumentFile: DocumentFile, getResultVolume: (DocumentFile) -> Volume?) { private fun copyVolume(srcDocumentFile: DocumentFile, dstDocumentFile: DocumentFile, getResultVolume: (DocumentFile) -> SavedVolume?) {
lifecycleScope.launch { lifecycleScope.launch {
val result = fileOperationService.copyVolume(srcDocumentFile, dstDocumentFile) val result = fileOperationService.copyVolume(srcDocumentFile, dstDocumentFile)
when { when {
@ -415,7 +419,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
} }
} }
private fun renameVolume(volume: Volume, position: Int) { private fun renameVolume(volume: SavedVolume, position: Int) {
with (EditTextDialog(this, R.string.new_volume_name) { newName -> with (EditTextDialog(this, R.string.new_volume_name) { newName ->
val srcPath = File(volume.getFullPath(filesDir.path)) val srcPath = File(volume.getFullPath(filesDir.path))
val dstPath = File(srcPath.parent, newName).canonicalFile val dstPath = File(srcPath.parent, newName).canonicalFile
@ -452,7 +456,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
} }
@SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23 @SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23
private fun openVolume(volume: Volume, position: Int) { private fun openVolume(volume: SavedVolume, position: Int) {
var askForPassword = true var askForPassword = true
fingerprintProtector?.let { fingerprintProtector -> fingerprintProtector?.let { fingerprintProtector ->
volume.encryptedHash?.let { encryptedHash -> volume.encryptedHash?.let { encryptedHash ->
@ -463,21 +467,21 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
volumeAdapter.refresh() volumeAdapter.refresh()
} }
override fun onPasswordHashDecrypted(hash: ByteArray) { override fun onPasswordHashDecrypted(hash: ByteArray) {
object : LoadingTask<Int>(this@MainActivity, themeValue, R.string.loading_msg_open) { object : LoadingTask<EncryptedVolume?>(this@MainActivity, themeValue, R.string.loading_msg_open) {
override suspend fun doTask(): Int { override suspend fun doTask(): EncryptedVolume? {
val sessionId = GocryptfsVolume.init(volume.getFullPath(filesDir.path), null, hash, null) val encryptedVolume = EncryptedVolume.init(volume, filesDir.path, null, hash, null)
Arrays.fill(hash, 0) Arrays.fill(hash, 0)
return sessionId return encryptedVolume
} }
}.startTask(lifecycleScope) { sessionId -> }.startTask(lifecycleScope) { encryptedVolume ->
if (sessionId != -1) { if (encryptedVolume == null) {
startExplorer(sessionId, volume.shortName)
} else {
CustomAlertDialogBuilder(this@MainActivity, themeValue) CustomAlertDialogBuilder(this@MainActivity, themeValue)
.setTitle(R.string.open_volume_failed) .setTitle(R.string.open_volume_failed)
.setMessage(R.string.open_failed_hash_msg) .setMessage(R.string.open_failed_hash_msg)
.setPositiveButton(R.string.ok, null) .setPositiveButton(R.string.ok, null)
.show() .show()
} else {
startExplorer(encryptedVolume, volume.shortName)
} }
} }
} }
@ -496,7 +500,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
askForPassword(volume, position) askForPassword(volume, position)
} }
private fun onPasswordSubmitted(volume: Volume, position: Int, dialogBinding: DialogOpenVolumeBinding) { private fun onPasswordSubmitted(volume: SavedVolume, position: Int, dialogBinding: DialogOpenVolumeBinding) {
if (dialogBinding.checkboxDefaultOpen.isChecked xor (defaultVolumeName == volume.name)) { if (dialogBinding.checkboxDefaultOpen.isChecked xor (defaultVolumeName == volume.name)) {
with (sharedPrefs.edit()) { with (sharedPrefs.edit()) {
defaultVolumeName = if (dialogBinding.checkboxDefaultOpen.isChecked) { defaultVolumeName = if (dialogBinding.checkboxDefaultOpen.isChecked) {
@ -515,12 +519,12 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
openVolumeWithPassword( openVolumeWithPassword(
volume, volume,
position, position,
password, StandardCharsets.UTF_8.encode(CharBuffer.wrap(password)).array(),
dialogBinding.checkboxSavePassword.isChecked, dialogBinding.checkboxSavePassword.isChecked,
) )
} }
private fun askForPassword(volume: Volume, position: Int, savePasswordHash: Boolean = false) { private fun askForPassword(volume: SavedVolume, position: Int, savePasswordHash: Boolean = false) {
val dialogBinding = DialogOpenVolumeBinding.inflate(layoutInflater) val dialogBinding = DialogOpenVolumeBinding.inflate(layoutInflater)
if (!usfFingerprint || fingerprintProtector == null || volume.encryptedHash != null) { if (!usfFingerprint || fingerprintProtector == null || volume.encryptedHash != null) {
dialogBinding.checkboxSavePassword.visibility = View.GONE dialogBinding.checkboxSavePassword.visibility = View.GONE
@ -550,20 +554,28 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
dialog.show() dialog.show()
} }
private fun openVolumeWithPassword(volume: Volume, position: Int, password: CharArray, savePasswordHash: Boolean) { private fun openVolumeWithPassword(volume: SavedVolume, position: Int, password: ByteArray, savePasswordHash: Boolean) {
val usfFingerprint = sharedPrefs.getBoolean("usf_fingerprint", false) val usfFingerprint = sharedPrefs.getBoolean("usf_fingerprint", false)
var returnedHash: ByteArray? = null var returnedHash: ByteArray? = null
if (savePasswordHash && usfFingerprint) { if (savePasswordHash && usfFingerprint) {
returnedHash = ByteArray(GocryptfsVolume.KeyLen) returnedHash = ByteArray(GocryptfsVolume.KeyLen)
} }
object : LoadingTask<Int>(this, themeValue, R.string.loading_msg_open) { object : LoadingTask<EncryptedVolume?>(this, themeValue, R.string.loading_msg_open) {
override suspend fun doTask(): Int { override suspend fun doTask(): EncryptedVolume? {
val sessionId = GocryptfsVolume.init(volume.getFullPath(filesDir.path), password, null, returnedHash) val encryptedVolume = EncryptedVolume.init(volume, filesDir.path, password, null, returnedHash)
Arrays.fill(password, 0.toChar()) Arrays.fill(password, 0)
return sessionId return encryptedVolume
} }
}.startTask(lifecycleScope) { sessionId -> }.startTask(lifecycleScope) { encryptedVolume ->
if (sessionId != -1) { if (encryptedVolume == null) {
CustomAlertDialogBuilder(this, themeValue)
.setTitle(R.string.open_volume_failed)
.setMessage(R.string.open_volume_failed_msg)
.setPositiveButton(R.string.ok) { _, _ ->
askForPassword(volume, position, savePasswordHash)
}
.show()
} else {
val fingerprintProtector = fingerprintProtector val fingerprintProtector = fingerprintProtector
@SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23 @SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23
if (savePasswordHash && returnedHash != null && fingerprintProtector != null) { if (savePasswordHash && returnedHash != null && fingerprintProtector != null) {
@ -575,12 +587,12 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
override fun onPasswordHashSaved() { override fun onPasswordHashSaved() {
Arrays.fill(returnedHash, 0) Arrays.fill(returnedHash, 0)
volumeAdapter.onVolumeChanged(position) volumeAdapter.onVolumeChanged(position)
startExplorer(sessionId, volume.shortName) startExplorer(encryptedVolume, volume.shortName)
} }
private var isClosed = false private var isClosed = false
override fun onFailed(pending: Boolean) { override fun onFailed(pending: Boolean) {
if (!isClosed) { if (!isClosed) {
GocryptfsVolume(this@MainActivity, sessionId).close() encryptedVolume.close()
isClosed = true isClosed = true
} }
Arrays.fill(returnedHash, 0) Arrays.fill(returnedHash, 0)
@ -588,21 +600,13 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
} }
fingerprintProtector.savePasswordHash(volume, returnedHash) fingerprintProtector.savePasswordHash(volume, returnedHash)
} else { } else {
startExplorer(sessionId, volume.shortName) startExplorer(encryptedVolume, volume.shortName)
} }
} else {
CustomAlertDialogBuilder(this, themeValue)
.setTitle(R.string.open_volume_failed)
.setMessage(R.string.open_volume_failed_msg)
.setPositiveButton(R.string.ok) { _, _ ->
askForPassword(volume, position, savePasswordHash)
}
.show()
} }
} }
} }
private fun startExplorer(sessionId: Int, volumeShortName: String) { private fun startExplorer(encryptedVolume: EncryptedVolume, volumeShortName: String) {
var explorerIntent: Intent? = null var explorerIntent: Intent? = null
if (dropMode) { //import via android share menu if (dropMode) { //import via android share menu
explorerIntent = Intent(this, ExplorerActivityDrop::class.java) explorerIntent = Intent(this, ExplorerActivityDrop::class.java)
@ -610,13 +614,13 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
explorerIntent.putExtras(intent.extras!!) //forward extras explorerIntent.putExtras(intent.extras!!) //forward extras
} else if (pickMode) { } else if (pickMode) {
explorerIntent = Intent(this, ExplorerActivityPick::class.java) explorerIntent = Intent(this, ExplorerActivityPick::class.java)
explorerIntent.putExtra("originalSessionID", intent.getIntExtra("sessionID", -1)) explorerIntent.putExtra("destinationVolume", intent.getParcelableExtra<EncryptedVolume>("volume")!!)
explorerIntent.flags = Intent.FLAG_ACTIVITY_FORWARD_RESULT explorerIntent.flags = Intent.FLAG_ACTIVITY_FORWARD_RESULT
} }
if (explorerIntent == null) { if (explorerIntent == null) {
explorerIntent = Intent(this, ExplorerActivity::class.java) //default opening explorerIntent = Intent(this, ExplorerActivity::class.java) //default opening
} }
explorerIntent.putExtra("sessionID", sessionId) explorerIntent.putExtra("volume", encryptedVolume)
explorerIntent.putExtra("volume_name", volumeShortName) explorerIntent.putExtra("volume_name", volumeShortName)
startActivity(explorerIntent) startActivity(explorerIntent)
if (pickMode) if (pickMode)
@ -642,7 +646,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
if (shouldCloseVolume) { if (shouldCloseVolume) {
val sessionID = intent.getIntExtra("sessionID", -1) val sessionID = intent.getIntExtra("sessionID", -1)
if (sessionID != -1) { if (sessionID != -1) {
GocryptfsVolume(this, sessionID).close() GocryptfsVolume(sessionID).close()
RestrictedFileProvider.wipeAll(this) RestrictedFileProvider.wipeAll(this)
} }
} }

View File

@ -5,11 +5,12 @@ import android.os.Parcelable
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
import java.io.File import java.io.File
class Volume(val name: String, val isHidden: Boolean = false, var encryptedHash: ByteArray? = null, var iv: ByteArray? = null): Parcelable { class SavedVolume(val name: String, val isHidden: Boolean = false, val type: Byte, var encryptedHash: ByteArray? = null, var iv: ByteArray? = null): Parcelable {
constructor(parcel: Parcel) : this( constructor(parcel: Parcel) : this(
parcel.readString()!!, parcel.readString()!!,
parcel.readByte() != 0.toByte(), parcel.readByte() != 0.toByte(),
parcel.readByte(),
parcel.createByteArray(), parcel.createByteArray(),
parcel.createByteArray() parcel.createByteArray()
) )
@ -20,7 +21,7 @@ class Volume(val name: String, val isHidden: Boolean = false, var encryptedHash:
fun getFullPath(filesDir: String): String { fun getFullPath(filesDir: String): String {
return if (isHidden) return if (isHidden)
PathUtils.pathJoin(filesDir, name) getHiddenVolumeFullPath(filesDir, name)
else else
name name
} }
@ -37,18 +38,23 @@ class Volume(val name: String, val isHidden: Boolean = false, var encryptedHash:
with (dest) { with (dest) {
writeString(name) writeString(name)
writeByte(if (isHidden) 1 else 0) writeByte(if (isHidden) 1 else 0)
writeByte(type)
writeByteArray(encryptedHash) writeByteArray(encryptedHash)
writeByteArray(iv) writeByteArray(iv)
} }
} }
companion object CREATOR : Parcelable.Creator<Volume> { companion object {
override fun createFromParcel(parcel: Parcel): Volume { const val VOLUMES_DIRECTORY = "volumes"
return Volume(parcel)
@JvmField
val CREATOR = object : Parcelable.Creator<SavedVolume> {
override fun createFromParcel(parcel: Parcel) = SavedVolume(parcel)
override fun newArray(size: Int) = arrayOfNulls<SavedVolume>(size)
} }
override fun newArray(size: Int): Array<Volume?> { fun getHiddenVolumeFullPath(filesDir: String, name: String): String {
return arrayOfNulls(size) return PathUtils.pathJoin(filesDir, VOLUMES_DIRECTORY, name)
} }
} }
} }

View File

@ -4,20 +4,25 @@ import android.content.ContentValues
import android.content.Context import android.content.Context
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper import android.database.sqlite.SQLiteOpenHelper
import android.util.Log
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.util.PathUtils
import java.io.File
class VolumeDatabase(context: Context): SQLiteOpenHelper(context, class VolumeDatabase(private val context: Context): SQLiteOpenHelper(context, ConstValues.VOLUME_DATABASE_NAME, null, 4) {
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"
const val COLUMN_HIDDEN = "hidden" const val COLUMN_HIDDEN = "hidden"
const val COLUMN_TYPE = "type"
const val COLUMN_HASH = "hash" const val COLUMN_HASH = "hash"
const val COLUMN_IV = "iv" const val COLUMN_IV = "iv"
private fun contentValuesFromVolume(volume: Volume): ContentValues { private fun contentValuesFromVolume(volume: SavedVolume): ContentValues {
val contentValues = ContentValues() val contentValues = ContentValues()
contentValues.put(COLUMN_NAME, volume.name) contentValues.put(COLUMN_NAME, volume.name)
contentValues.put(COLUMN_HIDDEN, volume.isHidden) contentValues.put(COLUMN_HIDDEN, volume.isHidden)
contentValues.put(COLUMN_TYPE, byteArrayOf(volume.type))
contentValues.put(COLUMN_HASH, volume.encryptedHash) contentValues.put(COLUMN_HASH, volume.encryptedHash)
contentValues.put(COLUMN_IV, volume.iv) contentValues.put(COLUMN_IV, volume.iv)
return contentValues return contentValues
@ -25,11 +30,57 @@ class VolumeDatabase(context: Context): SQLiteOpenHelper(context,
} }
override fun onCreate(db: SQLiteDatabase) { override fun onCreate(db: SQLiteDatabase) {
db.execSQL( db.execSQL(
"CREATE TABLE IF NOT EXISTS $TABLE_NAME ($COLUMN_NAME TEXT PRIMARY KEY, $COLUMN_HIDDEN SHORT, $COLUMN_HASH BLOB, $COLUMN_IV BLOB);" "CREATE TABLE IF NOT EXISTS $TABLE_NAME (" +
"$COLUMN_NAME TEXT PRIMARY KEY," +
"$COLUMN_HIDDEN SHORT," +
"$COLUMN_TYPE BLOB," +
"$COLUMN_HASH BLOB," +
"$COLUMN_IV BLOB" +
");"
) )
File(context.filesDir, SavedVolume.VOLUMES_DIRECTORY).mkdir()
} }
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {} override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
// Adding type column and set it to GOCRYPTFS_VOLUME_TYPE for all existing volumes
db.execSQL("ALTER TABLE $TABLE_NAME ADD COLUMN $COLUMN_TYPE BLOB;")
db.update(TABLE_NAME, ContentValues().apply {
put(COLUMN_TYPE, byteArrayOf(EncryptedVolume.GOCRYPTFS_VOLUME_TYPE))
}, null, null)
// Moving hidden volumes to the "volumes" directory
if (File(context.filesDir, SavedVolume.VOLUMES_DIRECTORY).mkdir()) {
val cursor = db.query(
TABLE_NAME,
arrayOf(COLUMN_NAME),
"$COLUMN_HIDDEN=?",
arrayOf("1"),
null,
null,
null
)
while (cursor.moveToNext()) {
val volumeName = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NAME))
File(
PathUtils.pathJoin(
context.filesDir.path,
volumeName
)
).renameTo(
File(
SavedVolume(
volumeName,
true,
EncryptedVolume.GOCRYPTFS_VOLUME_TYPE
).getFullPath(context.filesDir.path)
).canonicalFile
)
}
cursor.close()
} else {
Log.e("VolumeDatabase", "Volumes directory creation failed while upgrading")
}
}
fun isVolumeSaved(volumeName: String, isHidden: Boolean): Boolean { fun isVolumeSaved(volumeName: String, isHidden: Boolean): Boolean {
val cursor = readableDatabase.query(TABLE_NAME, val cursor = readableDatabase.query(TABLE_NAME,
@ -42,23 +93,30 @@ class VolumeDatabase(context: Context): SQLiteOpenHelper(context,
return result return result
} }
fun saveVolume(volume: Volume): Boolean { fun saveVolume(volume: SavedVolume): Boolean {
if (!isVolumeSaved(volume.name, volume.isHidden)) { if (!isVolumeSaved(volume.name, volume.isHidden)) {
return (writableDatabase.insert(TABLE_NAME, null, contentValuesFromVolume(volume)) == 0.toLong()) return (writableDatabase.insert(TABLE_NAME, null, contentValuesFromVolume(volume)) == 0.toLong())
} }
return false return false
} }
fun getVolumes(): List<Volume> { fun getVolumes(): List<SavedVolume> {
val list: MutableList<Volume> = ArrayList() val list: MutableList<SavedVolume> = ArrayList()
val cursor = readableDatabase.rawQuery("SELECT * FROM $TABLE_NAME", null) val cursor = readableDatabase.rawQuery("SELECT * FROM $TABLE_NAME", null)
while (cursor.moveToNext()){ while (cursor.moveToNext()){
val typeColumnIndex = cursor.getColumnIndex(COLUMN_TYPE)
val volumeType = if (typeColumnIndex == -1) {
EncryptedVolume.GOCRYPTFS_VOLUME_TYPE
} else {
cursor.getBlob(typeColumnIndex)[0]
}
list.add( list.add(
Volume( SavedVolume(
cursor.getString(cursor.getColumnIndex(COLUMN_NAME)), cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NAME)),
cursor.getShort(cursor.getColumnIndex(COLUMN_HIDDEN)) == 1.toShort(), cursor.getShort(cursor.getColumnIndexOrThrow(COLUMN_HIDDEN)) == 1.toShort(),
cursor.getBlob(cursor.getColumnIndex(COLUMN_HASH)), volumeType,
cursor.getBlob(cursor.getColumnIndex(COLUMN_IV)) cursor.getBlob(cursor.getColumnIndexOrThrow(COLUMN_HASH)),
cursor.getBlob(cursor.getColumnIndexOrThrow(COLUMN_IV))
) )
) )
} }
@ -70,7 +128,7 @@ class VolumeDatabase(context: Context): SQLiteOpenHelper(context,
val cursor = readableDatabase.query(TABLE_NAME, arrayOf(COLUMN_NAME, COLUMN_HASH), "$COLUMN_NAME=?", arrayOf(volumeName), null, null, null) val cursor = readableDatabase.query(TABLE_NAME, arrayOf(COLUMN_NAME, COLUMN_HASH), "$COLUMN_NAME=?", arrayOf(volumeName), null, null, null)
var isHashSaved = false var isHashSaved = false
if (cursor.moveToNext()) { if (cursor.moveToNext()) {
if (cursor.getBlob(cursor.getColumnIndex(COLUMN_HASH)) != null) { if (cursor.getBlob(cursor.getColumnIndexOrThrow(COLUMN_HASH)) != null) {
isHashSaved = true isHashSaved = true
} }
} }
@ -78,16 +136,17 @@ class VolumeDatabase(context: Context): SQLiteOpenHelper(context,
return isHashSaved return isHashSaved
} }
fun addHash(volume: Volume): Boolean { fun addHash(volume: SavedVolume): Boolean {
return writableDatabase.update(TABLE_NAME, contentValuesFromVolume(volume), "$COLUMN_NAME=?", arrayOf(volume.name)) > 0 return writableDatabase.update(TABLE_NAME, contentValuesFromVolume(volume), "$COLUMN_NAME=?", arrayOf(volume.name)) > 0
} }
fun removeHash(volume: Volume): Boolean { fun removeHash(volume: SavedVolume): Boolean {
return writableDatabase.update( return writableDatabase.update(
TABLE_NAME, contentValuesFromVolume( TABLE_NAME, contentValuesFromVolume(
Volume( SavedVolume(
volume.name, volume.name,
volume.isHidden, volume.isHidden,
volume.type,
null, null,
null null
) )

View File

@ -17,16 +17,17 @@ import com.bumptech.glide.request.target.DrawableImageViewTarget
import com.bumptech.glide.request.transition.Transition import com.bumptech.glide.request.transition.Transition
import kotlinx.coroutines.* import kotlinx.coroutines.*
import sushi.hardcore.droidfs.ConstValues import sushi.hardcore.droidfs.ConstValues
import sushi.hardcore.droidfs.GocryptfsVolume
import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.explorers.ExplorerElement import sushi.hardcore.droidfs.explorers.ExplorerElement
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.filesystems.Stat
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
import java.text.DateFormat import java.text.DateFormat
import java.util.* import java.util.*
class ExplorerElementAdapter( class ExplorerElementAdapter(
val activity: AppCompatActivity, val activity: AppCompatActivity,
val gocryptfsVolume: GocryptfsVolume?, val encryptedVolume: EncryptedVolume?,
private val listener: Listener, private val listener: Listener,
val thumbnailMaxSize: Long, val thumbnailMaxSize: Long,
) : SelectableAdapter<ExplorerElement>(listener::onSelectionChanged) { ) : SelectableAdapter<ExplorerElement>(listener::onSelectionChanged) {
@ -42,7 +43,7 @@ class ExplorerElementAdapter(
private var thumbnailsCache: LruCache<String, Bitmap>? = null private var thumbnailsCache: LruCache<String, Bitmap>? = null
init { init {
if (gocryptfsVolume != null) { if (encryptedVolume != null) {
thumbnailsCache = LruCache((Runtime.getRuntime().maxMemory() / 1024 / 8).toInt()) thumbnailsCache = LruCache((Runtime.getRuntime().maxMemory() / 1024 / 8).toInt())
} }
} }
@ -105,9 +106,9 @@ class ExplorerElementAdapter(
open class RegularElementViewHolder(itemView: View) : ExplorerElementViewHolder(itemView) { open class RegularElementViewHolder(itemView: View) : ExplorerElementViewHolder(itemView) {
open fun bind(explorerElement: ExplorerElement, position: Int, isSelected: Boolean) { open fun bind(explorerElement: ExplorerElement, position: Int, isSelected: Boolean) {
super.bind(explorerElement, position) super.bind(explorerElement, position)
textElementSize.text = PathUtils.formatSize(explorerElement.size) textElementSize.text = PathUtils.formatSize(explorerElement.stat.size)
(bindingAdapter as ExplorerElementAdapter?)?.let { (bindingAdapter as ExplorerElementAdapter?)?.let {
textElementMtime.text = it.dateFormat.format(explorerElement.mTime) textElementMtime.text = it.dateFormat.format(explorerElement.stat.mTime)
} }
} }
} }
@ -118,7 +119,7 @@ class ExplorerElementAdapter(
private val scope = CoroutineScope(Dispatchers.IO) private val scope = CoroutineScope(Dispatchers.IO)
private fun loadThumbnail(fullPath: String, adapter: ExplorerElementAdapter) { private fun loadThumbnail(fullPath: String, adapter: ExplorerElementAdapter) {
adapter.gocryptfsVolume?.let { volume -> adapter.encryptedVolume?.let { volume ->
job = scope.launch { job = scope.launch {
volume.loadWholeFile(fullPath, maxSize = adapter.thumbnailMaxSize).first?.let { volume.loadWholeFile(fullPath, maxSize = adapter.thumbnailMaxSize).first?.let {
if (isActive) { if (isActive) {
@ -220,9 +221,9 @@ class ExplorerElementAdapter(
}, parent, false }, parent, false
) )
return when (viewType) { return when (viewType) {
ExplorerElement.REGULAR_FILE_TYPE -> FileViewHolder(view) Stat.S_IFREG -> FileViewHolder(view)
ExplorerElement.DIRECTORY_TYPE -> DirectoryViewHolder(view) Stat.S_IFDIR -> DirectoryViewHolder(view)
ExplorerElement.PARENT_FOLDER_TYPE -> ParentFolderViewHolder(view) Stat.PARENT_FOLDER_TYPE -> ParentFolderViewHolder(view)
else -> throw IllegalArgumentException() else -> throw IllegalArgumentException()
} }
} }
@ -237,6 +238,6 @@ class ExplorerElementAdapter(
} }
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
return explorerElements[position].elementType.toInt() return explorerElements[position].stat.type
} }
} }

View File

@ -10,7 +10,7 @@ import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.Volume import sushi.hardcore.droidfs.SavedVolume
import sushi.hardcore.droidfs.VolumeDatabase import sushi.hardcore.droidfs.VolumeDatabase
class VolumeAdapter( class VolumeAdapter(
@ -19,9 +19,9 @@ class VolumeAdapter(
private val allowSelection: Boolean, private val allowSelection: Boolean,
private val showReadOnly: Boolean, private val showReadOnly: Boolean,
private val listener: Listener, private val listener: Listener,
) : SelectableAdapter<Volume>(listener::onSelectionChanged) { ) : SelectableAdapter<SavedVolume>(listener::onSelectionChanged) {
private val inflater: LayoutInflater = LayoutInflater.from(context) private val inflater: LayoutInflater = LayoutInflater.from(context)
lateinit var volumes: List<Volume> lateinit var volumes: List<SavedVolume>
init { init {
reloadVolumes() reloadVolumes()
@ -29,11 +29,11 @@ class VolumeAdapter(
interface Listener { interface Listener {
fun onSelectionChanged(size: Int) fun onSelectionChanged(size: Int)
fun onVolumeItemClick(volume: Volume, position: Int) fun onVolumeItemClick(volume: SavedVolume, position: Int)
fun onVolumeItemLongClick() fun onVolumeItemLongClick()
} }
override fun getItems(): List<Volume> { override fun getItems(): List<SavedVolume> {
return volumes return volumes
} }

View File

@ -15,6 +15,7 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import sushi.hardcore.droidfs.* import sushi.hardcore.droidfs.*
import sushi.hardcore.droidfs.databinding.FragmentCreateVolumeBinding import sushi.hardcore.droidfs.databinding.FragmentCreateVolumeBinding
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
import java.io.File import java.io.File
import java.util.* import java.util.*
@ -130,8 +131,8 @@ 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)
object: LoadingTask<Volume?>(requireActivity() as AppCompatActivity, themeValue, R.string.loading_msg_create) { object: LoadingTask<SavedVolume?>(requireActivity() as AppCompatActivity, themeValue, R.string.loading_msg_create) {
override suspend fun doTask(): Volume? { override suspend fun doTask(): SavedVolume? {
val xchacha = when (binding.spinnerXchacha.selectedItemPosition) { val xchacha = when (binding.spinnerXchacha.selectedItemPosition) {
0 -> 0 0 -> 0
1 -> 1 1 -> 1
@ -151,7 +152,7 @@ class CreateVolumeFragment: Fragment() {
) )
) { ) {
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 = SavedVolume(volumeName, isHiddenVolume, EncryptedVolume.GOCRYPTFS_VOLUME_TYPE)
volumeDatabase.apply { volumeDatabase.apply {
if (isVolumeSaved(volumeName, isHiddenVolume)) // cleaning old saved path if (isVolumeSaved(volumeName, isHiddenVolume)) // cleaning old saved path
removeVolume(volumeName) removeVolume(volumeName)

View File

@ -11,7 +11,6 @@ import android.text.TextWatcher
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@ -19,6 +18,7 @@ import androidx.fragment.app.Fragment
import sushi.hardcore.droidfs.* import sushi.hardcore.droidfs.*
import sushi.hardcore.droidfs.databinding.DialogSdcardErrorBinding import sushi.hardcore.droidfs.databinding.DialogSdcardErrorBinding
import sushi.hardcore.droidfs.databinding.FragmentSelectPathBinding import sushi.hardcore.droidfs.databinding.FragmentSelectPathBinding
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
import java.io.File import java.io.File
@ -158,7 +158,7 @@ class SelectPathFragment: Fragment() {
private fun getCurrentVolumePath(): String { private fun getCurrentVolumePath(): String {
return if (binding.switchHiddenVolume.isChecked) return if (binding.switchHiddenVolume.isChecked)
PathUtils.pathJoin(requireContext().filesDir.path, binding.editVolumeName.text.toString()) SavedVolume.getHiddenVolumeFullPath(requireContext().filesDir.path, binding.editVolumeName.text.toString())
else else
binding.editVolumeName.text.toString() binding.editVolumeName.text.toString()
} }
@ -222,7 +222,8 @@ class SelectPathFragment: Fragment() {
(activity as AddVolumeActivity).createVolume(volumePath, isHidden) (activity as AddVolumeActivity).createVolume(volumePath, isHidden)
} }
Action.ADD -> { Action.ADD -> {
if (!GocryptfsVolume.isGocryptfsVolume(File(volumePath))) { val volumeType = EncryptedVolume.getVolumeType(volumePath)
if (volumeType < 0) {
CustomAlertDialogBuilder(requireContext(), themeValue) CustomAlertDialogBuilder(requireContext(), themeValue)
.setTitle(R.string.error) .setTitle(R.string.error)
.setMessage(R.string.error_not_a_volume) .setMessage(R.string.error_not_a_volume)
@ -232,7 +233,7 @@ class SelectPathFragment: Fragment() {
val dialog = CustomAlertDialogBuilder(requireContext(), themeValue) val dialog = CustomAlertDialogBuilder(requireContext(), themeValue)
.setTitle(R.string.warning) .setTitle(R.string.warning)
.setCancelable(false) .setCancelable(false)
.setPositiveButton(R.string.ok) { _, _ -> addVolume(if (isHidden) currentVolumeValue else volumePath, isHidden) } .setPositiveButton(R.string.ok) { _, _ -> addVolume(if (isHidden) currentVolumeValue else volumePath, isHidden, volumeType) }
if (PathUtils.isPathOnExternalStorage(volumePath, requireContext())) if (PathUtils.isPathOnExternalStorage(volumePath, requireContext()))
dialog.setView( dialog.setView(
DialogSdcardErrorBinding.inflate(layoutInflater).apply { DialogSdcardErrorBinding.inflate(layoutInflater).apply {
@ -244,7 +245,7 @@ class SelectPathFragment: Fragment() {
dialog.setMessage(R.string.add_cant_write_warning) dialog.setMessage(R.string.add_cant_write_warning)
dialog.show() dialog.show()
} else { } else {
addVolume(if (isHidden) currentVolumeValue else volumePath, isHidden) addVolume(if (isHidden) currentVolumeValue else volumePath, isHidden, volumeType)
} }
} }
} }
@ -268,8 +269,8 @@ class SelectPathFragment: Fragment() {
dialog.show() dialog.show()
} }
private fun addVolume(volumeName: String, isHidden: Boolean) { private fun addVolume(volumeName: String, isHidden: Boolean, volumeType: Byte) {
volumeDatabase.saveVolume(Volume(volumeName, isHidden)) volumeDatabase.saveVolume(SavedVolume(volumeName, isHidden, volumeType))
(activity as AddVolumeActivity).onVolumeAdded(false) (activity as AddVolumeActivity).onVolumeAdded(false)
} }
} }

View File

@ -9,9 +9,9 @@ import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import sushi.hardcore.droidfs.GocryptfsVolume
import sushi.hardcore.droidfs.LoadingTask import sushi.hardcore.droidfs.LoadingTask
import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
import java.io.File import java.io.File
@ -33,25 +33,25 @@ object ExternalProvider {
return previous_content_type return previous_content_type
} }
private fun exportFile(context: Context, gocryptfsVolume: GocryptfsVolume, file_path: String, previous_content_type: String?): Pair<Uri?, String?> { private fun exportFile(context: Context, encryptedVolume: EncryptedVolume, file_path: String, previous_content_type: String?): Pair<Uri?, String?> {
val fileName = File(file_path).name val fileName = File(file_path).name
val tmpFileUri = RestrictedFileProvider.newFile(fileName) val tmpFileUri = RestrictedFileProvider.newFile(fileName)
if (tmpFileUri != null){ if (tmpFileUri != null){
storedFiles.add(tmpFileUri) storedFiles.add(tmpFileUri)
if (gocryptfsVolume.exportFile(context, file_path, tmpFileUri)) { if (encryptedVolume.exportFile(context, file_path, tmpFileUri)) {
return Pair(tmpFileUri, getContentType(fileName, previous_content_type)) return Pair(tmpFileUri, getContentType(fileName, previous_content_type))
} }
} }
return Pair(null, null) return Pair(null, null)
} }
fun share(activity: AppCompatActivity, themeValue: String, gocryptfsVolume: GocryptfsVolume, file_paths: List<String>) { fun share(activity: AppCompatActivity, themeValue: String, encryptedVolume: EncryptedVolume, file_paths: List<String>) {
var contentType: String? = null var contentType: String? = null
val uris = ArrayList<Uri>(file_paths.size) val uris = ArrayList<Uri>(file_paths.size)
object : LoadingTask<String?>(activity, themeValue, R.string.loading_msg_export) { object : LoadingTask<String?>(activity, themeValue, R.string.loading_msg_export) {
override suspend fun doTask(): String? { override suspend fun doTask(): String? {
for (path in file_paths) { for (path in file_paths) {
val result = exportFile(activity, gocryptfsVolume, path, contentType) val result = exportFile(activity, encryptedVolume, path, contentType)
contentType = if (result.first != null) { contentType = if (result.first != null) {
uris.add(result.first!!) uris.add(result.first!!)
result.second result.second
@ -83,10 +83,10 @@ object ExternalProvider {
} }
} }
fun open(activity: AppCompatActivity, themeValue: String, gocryptfsVolume: GocryptfsVolume, file_path: String) { fun open(activity: AppCompatActivity, themeValue: String, encryptedVolume: EncryptedVolume, file_path: String) {
object : LoadingTask<Intent?>(activity, themeValue, R.string.loading_msg_export) { object : LoadingTask<Intent?>(activity, themeValue, R.string.loading_msg_export) {
override suspend fun doTask(): Intent? { override suspend fun doTask(): Intent? {
val result = exportFile(activity, gocryptfsVolume, file_path, null) val result = exportFile(activity, encryptedVolume, file_path, null)
return if (result.first != null) { return if (result.first != null) {
Intent(Intent.ACTION_VIEW).apply { Intent(Intent.ACTION_VIEW).apply {
setDataAndType(result.first, result.second) setDataAndType(result.first, result.second)

View File

@ -7,6 +7,7 @@ import android.content.ServiceConnection
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.IBinder import android.os.IBinder
import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
@ -40,6 +41,8 @@ import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
import sushi.hardcore.droidfs.file_operations.FileOperationService import sushi.hardcore.droidfs.file_operations.FileOperationService
import sushi.hardcore.droidfs.file_operations.OperationFile import sushi.hardcore.droidfs.file_operations.OperationFile
import sushi.hardcore.droidfs.file_viewers.* import sushi.hardcore.droidfs.file_viewers.*
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.filesystems.Stat
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
import sushi.hardcore.droidfs.widgets.EditTextDialog import sushi.hardcore.droidfs.widgets.EditTextDialog
@ -50,7 +53,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
private var foldersFirst = true private var foldersFirst = true
private var mapFolders = true private var mapFolders = true
private var currentSortOrderIndex = 0 private var currentSortOrderIndex = 0
protected lateinit var gocryptfsVolume: GocryptfsVolume protected lateinit var encryptedVolume: EncryptedVolume
private lateinit var volumeName: String private lateinit var volumeName: String
private lateinit var explorerViewModel: ExplorerViewModel private lateinit var explorerViewModel: ExplorerViewModel
protected var currentDirectoryPath: String = "" protected var currentDirectoryPath: String = ""
@ -82,8 +85,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
usf_open = sharedPrefs.getBoolean("usf_open", false) usf_open = sharedPrefs.getBoolean("usf_open", false)
usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false) usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false)
volumeName = intent.getStringExtra("volume_name") ?: "" volumeName = intent.getStringExtra("volume_name") ?: ""
val sessionID = intent.getIntExtra("sessionID", -1) encryptedVolume = intent.getParcelableExtra("volume")!!
gocryptfsVolume = GocryptfsVolume(applicationContext, sessionID)
sortOrderEntries = resources.getStringArray(R.array.sort_orders_entries) sortOrderEntries = resources.getStringArray(R.array.sort_orders_entries)
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)
@ -107,7 +109,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
explorerAdapter = ExplorerElementAdapter( explorerAdapter = ExplorerElementAdapter(
this, this,
if (sharedPrefs.getBoolean("thumbnails", true)) { if (sharedPrefs.getBoolean("thumbnails", true)) {
gocryptfsVolume encryptedVolume
} else { } else {
null null
}, },
@ -139,7 +141,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
} }
class ExplorerViewModel: ViewModel() { class ExplorerViewModel: ViewModel() {
var currentDirectoryPath = "" var currentDirectoryPath = "/"
} }
private fun setRecyclerViewLayout() { private fun setRecyclerViewLayout() {
@ -166,7 +168,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
override fun onServiceConnected(className: ComponentName, service: IBinder) { override fun onServiceConnected(className: ComponentName, service: IBinder) {
val binder = service as FileOperationService.LocalBinder val binder = service as FileOperationService.LocalBinder
fileOperationService = binder.getService() fileOperationService = binder.getService()
binder.setGocryptfsVolume(gocryptfsVolume) binder.setEncryptedVolume(encryptedVolume)
} }
override fun onServiceDisconnected(arg0: ComponentName) { override fun onServiceDisconnected(arg0: ComponentName) {
@ -178,7 +180,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
private fun startFileViewer(cls: Class<*>, filePath: String){ private fun startFileViewer(cls: Class<*>, filePath: String){
val intent = Intent(this, cls).apply { val intent = Intent(this, cls).apply {
putExtra("path", filePath) putExtra("path", filePath)
putExtra("sessionID", gocryptfsVolume.sessionID) putExtra("volume", encryptedVolume)
putExtra("sortOrder", sortOrderValues[currentSortOrderIndex]) putExtra("sortOrder", sortOrderValues[currentSortOrderIndex])
} }
isStartingActivity = true isStartingActivity = true
@ -187,7 +189,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
private fun openWithExternalApp(fullPath: String){ private fun openWithExternalApp(fullPath: String){
isStartingActivity = true isStartingActivity = true
ExternalProvider.open(this, themeValue, gocryptfsVolume, fullPath) ExternalProvider.open(this, themeValue, encryptedVolume, fullPath)
} }
private fun showOpenAsDialog(path: String) { private fun showOpenAsDialog(path: String) {
@ -276,11 +278,11 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
} }
private fun recursiveSetSize(directory: ExplorerElement) { private fun recursiveSetSize(directory: ExplorerElement) {
for (child in gocryptfsVolume.listDir(directory.fullPath)) { for (child in encryptedVolume.readDir(directory.fullPath) ?: return) {
if (child.isDirectory) { if (child.isDirectory) {
recursiveSetSize(child) recursiveSetSize(child)
} }
directory.size += child.size directory.stat.size += child.stat.size
} }
} }
@ -301,11 +303,11 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
protected fun setCurrentPath(path: String, onDisplayed: (() -> Unit)? = null) { protected fun setCurrentPath(path: String, onDisplayed: (() -> Unit)? = null) {
synchronized(this) { synchronized(this) {
explorerElements = gocryptfsVolume.listDir(path) explorerElements = encryptedVolume.readDir(path) ?: return
if (path.isNotEmpty()) { //not root if (path != "/") {
explorerElements.add( explorerElements.add(
0, 0,
ExplorerElement("..", (-1).toShort(), parentPath = currentDirectoryPath) ExplorerElement("..", Stat.parentFolderStat(), parentPath = currentDirectoryPath)
) )
} }
} }
@ -323,7 +325,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
if (element.isDirectory) { if (element.isDirectory) {
recursiveSetSize(element) recursiveSetSize(element)
} }
totalSize += element.size totalSize += element.stat.size
} }
} }
} }
@ -331,7 +333,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
onDisplayed?.invoke() onDisplayed?.invoke()
} }
} else { } else {
displayExplorerElements(explorerElements.filter { !it.isParentFolder }.sumOf { it.size }) displayExplorerElements(explorerElements.filter { !it.isParentFolder }.sumOf { it.stat.size })
onDisplayed?.invoke() onDisplayed?.invoke()
} }
} }
@ -362,7 +364,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
if (folderName.isEmpty()) { if (folderName.isEmpty()) {
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
} else { } else {
if (!gocryptfsVolume.mkdir(PathUtils.pathJoin(currentDirectoryPath, folderName))) { if (!encryptedVolume.mkdir(PathUtils.pathJoin(currentDirectoryPath, folderName))) {
CustomAlertDialogBuilder(this, themeValue) CustomAlertDialogBuilder(this, themeValue)
.setTitle(R.string.error) .setTitle(R.string.error)
.setMessage(R.string.error_mkdir) .setMessage(R.string.error_mkdir)
@ -382,27 +384,27 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
} }
protected fun checkPathOverwrite(items: ArrayList<OperationFile>, dstDirectoryPath: String, callback: (ArrayList<OperationFile>?) -> Unit) { protected fun checkPathOverwrite(items: ArrayList<OperationFile>, dstDirectoryPath: String, callback: (ArrayList<OperationFile>?) -> Unit) {
val srcDirectoryPath = items[0].explorerElement.parentPath val srcDirectoryPath = items[0].parentPath
var ready = true var ready = true
for (i in 0 until items.size) { for (i in 0 until items.size) {
val testDstPath: String val testDstPath: String
if (items[i].dstPath == null){ if (items[i].dstPath == null){
testDstPath = PathUtils.pathJoin(dstDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, items[i].explorerElement.fullPath)) testDstPath = PathUtils.pathJoin(dstDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, items[i].srcPath))
if (gocryptfsVolume.pathExists(testDstPath)){ if (encryptedVolume.pathExists(testDstPath)) {
ready = false ready = false
} else { } else {
items[i].dstPath = testDstPath items[i].dstPath = testDstPath
} }
} else { } else {
testDstPath = items[i].dstPath!! testDstPath = items[i].dstPath!!
if (gocryptfsVolume.pathExists(testDstPath) && !items[i].overwriteConfirmed){ if (encryptedVolume.pathExists(testDstPath) && !items[i].overwriteConfirmed) {
ready = false ready = false
} }
} }
if (!ready){ if (!ready){
CustomAlertDialogBuilder(this, themeValue) CustomAlertDialogBuilder(this, themeValue)
.setTitle(R.string.warning) .setTitle(R.string.warning)
.setMessage(getString(if (items[i].explorerElement.isDirectory){R.string.dir_overwrite_question} else {R.string.file_overwrite_question}, testDstPath)) .setMessage(getString(if (items[i].isDirectory){R.string.dir_overwrite_question} else {R.string.file_overwrite_question}, testDstPath))
.setPositiveButton(R.string.yes) {_, _ -> .setPositiveButton(R.string.yes) {_, _ ->
items[i].dstPath = testDstPath items[i].dstPath = testDstPath
items[i].overwriteConfirmed = true items[i].overwriteConfirmed = true
@ -410,17 +412,17 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
} }
.setNegativeButton(R.string.no) { _, _ -> .setNegativeButton(R.string.no) { _, _ ->
with(EditTextDialog(this, R.string.enter_new_name) { with(EditTextDialog(this, R.string.enter_new_name) {
items[i].dstPath = PathUtils.pathJoin(dstDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, items[i].explorerElement.parentPath), it) items[i].dstPath = PathUtils.pathJoin(dstDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, items[i].parentPath), it)
if (items[i].explorerElement.isDirectory){ if (items[i].isDirectory) {
for (j in 0 until items.size){ for (j in 0 until items.size){
if (PathUtils.isChildOf(items[j].explorerElement.fullPath, items[i].explorerElement.fullPath)){ if (PathUtils.isChildOf(items[j].srcPath, items[i].srcPath)) {
items[j].dstPath = PathUtils.pathJoin(items[i].dstPath!!, PathUtils.getRelativePath(items[i].explorerElement.fullPath, items[j].explorerElement.fullPath)) items[j].dstPath = PathUtils.pathJoin(items[i].dstPath!!, PathUtils.getRelativePath(items[i].srcPath, items[j].srcPath))
} }
} }
} }
checkPathOverwrite(items, dstDirectoryPath, callback) checkPathOverwrite(items, dstDirectoryPath, callback)
}) { }) {
setSelectedText(items[i].explorerElement.name) setSelectedText(items[i].name)
setOnCancelListener{ setOnCancelListener{
callback(null) callback(null)
} }
@ -452,7 +454,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
items.clear() items.clear()
break break
} else { } else {
items.add(OperationFile.fromExplorerElement(ExplorerElement(fileName, 1, parentPath = currentDirectoryPath))) items.add(OperationFile(PathUtils.pathJoin(fileName, currentDirectoryPath), Stat.S_IFREG))
} }
} }
if (items.size > 0) { if (items.size > 0) {
@ -475,7 +477,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
if (new_name.isEmpty()) { if (new_name.isEmpty()) {
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
} else { } else {
if (!gocryptfsVolume.rename(PathUtils.pathJoin(currentDirectoryPath, old_name), PathUtils.pathJoin(currentDirectoryPath, new_name))) { if (!encryptedVolume.rename(PathUtils.pathJoin(currentDirectoryPath, old_name), PathUtils.pathJoin(currentDirectoryPath, new_name))) {
CustomAlertDialogBuilder(this, themeValue) CustomAlertDialogBuilder(this, themeValue)
.setTitle(R.string.error) .setTitle(R.string.error)
.setMessage(getString(R.string.rename_failed, old_name)) .setMessage(getString(R.string.rename_failed, old_name))
@ -587,8 +589,8 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
} }
protected open fun closeVolumeOnDestroy() { protected open fun closeVolumeOnDestroy() {
if (!gocryptfsVolume.isClosed()){ if (!encryptedVolume.isClosed()){
gocryptfsVolume.close() encryptedVolume.close()
} }
RestrictedFileProvider.wipeAll(this) //additional security RestrictedFileProvider.wipeAll(this) //additional security
} }
@ -616,7 +618,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
if (isCreating){ if (isCreating){
isCreating = false isCreating = false
} else { } else {
if (gocryptfsVolume.isClosed()){ if (encryptedVolume.isClosed()){
finish() finish()
} else { } else {
isStartingActivity = false isStartingActivity = false

View File

@ -3,6 +3,7 @@ package sushi.hardcore.droidfs.explorers
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.widget.Toast import android.widget.Toast
@ -18,6 +19,8 @@ import sushi.hardcore.droidfs.adapters.IconTextDialogAdapter
import sushi.hardcore.droidfs.content_providers.ExternalProvider import sushi.hardcore.droidfs.content_providers.ExternalProvider
import sushi.hardcore.droidfs.databinding.ActivityExplorerBinding import sushi.hardcore.droidfs.databinding.ActivityExplorerBinding
import sushi.hardcore.droidfs.file_operations.OperationFile import sushi.hardcore.droidfs.file_operations.OperationFile
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.filesystems.Stat
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
import sushi.hardcore.droidfs.widgets.EditTextDialog import sushi.hardcore.droidfs.widgets.EditTextDialog
@ -36,8 +39,7 @@ class ExplorerActivity : BaseExplorerActivity() {
private val pickFromOtherVolumes = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> private val pickFromOtherVolumes = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) { if (result.resultCode == Activity.RESULT_OK) {
result.data?.let { resultIntent -> result.data?.let { resultIntent ->
val remoteSessionID = resultIntent.getIntExtra("sessionID", -1) val remoteEncryptedVolume = resultIntent.getParcelableExtra<EncryptedVolume>("volume")!!
val remoteGocryptfsVolume = GocryptfsVolume(applicationContext, remoteSessionID)
val path = resultIntent.getStringExtra("path") val path = resultIntent.getStringExtra("path")
val operationFiles = ArrayList<OperationFile>() val operationFiles = ArrayList<OperationFile>()
if (path == null){ //multiples elements if (path == null){ //multiples elements
@ -46,12 +48,10 @@ class ExplorerActivity : BaseExplorerActivity() {
if (types != null && paths != null){ if (types != null && paths != null){
for (i in paths.indices) { for (i in paths.indices) {
operationFiles.add( operationFiles.add(
OperationFile.fromExplorerElement( OperationFile(paths[i], types[i])
ExplorerElement(File(paths[i]).name, types[i].toShort(), parentPath = PathUtils.getParentPath(paths[i]))
)
) )
if (types[i] == 0){ //directory if (types[i] == Stat.S_IFDIR) {
remoteGocryptfsVolume.recursiveMapFiles(paths[i]).forEach { remoteEncryptedVolume.recursiveMapFiles(paths[i])?.forEach {
operationFiles.add(OperationFile.fromExplorerElement(it)) operationFiles.add(OperationFile.fromExplorerElement(it))
} }
} }
@ -59,18 +59,16 @@ class ExplorerActivity : BaseExplorerActivity() {
} }
} else { } else {
operationFiles.add( operationFiles.add(
OperationFile.fromExplorerElement( OperationFile(path, Stat.S_IFREG)
ExplorerElement(File(path).name, 1, parentPath = PathUtils.getParentPath(path))
)
) )
} }
if (operationFiles.size > 0){ if (operationFiles.size > 0){
checkPathOverwrite(operationFiles, currentDirectoryPath) { items -> checkPathOverwrite(operationFiles, currentDirectoryPath) { items ->
if (items == null) { if (items == null) {
remoteGocryptfsVolume.close() remoteEncryptedVolume.close()
} else { } else {
lifecycleScope.launch { lifecycleScope.launch {
val failedItem = fileOperationService.copyElements(items, remoteGocryptfsVolume) val failedItem = fileOperationService.copyElements(items, remoteEncryptedVolume)
if (failedItem == null) { if (failedItem == null) {
Toast.makeText(this@ExplorerActivity, R.string.success_import, Toast.LENGTH_SHORT).show() Toast.makeText(this@ExplorerActivity, R.string.success_import, Toast.LENGTH_SHORT).show()
} else { } else {
@ -81,12 +79,12 @@ class ExplorerActivity : BaseExplorerActivity() {
.show() .show()
} }
setCurrentPath(currentDirectoryPath) setCurrentPath(currentDirectoryPath)
remoteGocryptfsVolume.close() remoteEncryptedVolume.close()
} }
} }
} }
} else { } else {
remoteGocryptfsVolume.close() remoteEncryptedVolume.close()
} }
} }
} }
@ -120,7 +118,7 @@ class ExplorerActivity : BaseExplorerActivity() {
private val pickImportDirectory = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { rootUri -> private val pickImportDirectory = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { rootUri ->
rootUri?.let { rootUri?.let {
val tree = DocumentFile.fromTreeUri(this, it)!! //non-null after Lollipop val tree = DocumentFile.fromTreeUri(this, it)!! //non-null after Lollipop
val operation = OperationFile.fromExplorerElement(ExplorerElement(tree.name!!, 0, parentPath = currentDirectoryPath)) val operation = OperationFile(PathUtils.pathJoin(tree.name!!, currentDirectoryPath), Stat.S_IFDIR)
checkPathOverwrite(arrayListOf(operation), currentDirectoryPath) { checkedOperation -> checkPathOverwrite(arrayListOf(operation), currentDirectoryPath) { checkedOperation ->
checkedOperation?.let { checkedOperation?.let {
lifecycleScope.launch { lifecycleScope.launch {
@ -175,7 +173,7 @@ class ExplorerActivity : BaseExplorerActivity() {
setContentView(binding.root) setContentView(binding.root)
binding.fab.setOnClickListener { binding.fab.setOnClickListener {
if (currentItemAction != ItemsActions.NONE){ if (currentItemAction != ItemsActions.NONE){
openDialogCreateFolder() //openDialogCreateFolder()
} else { } else {
val adapter = IconTextDialogAdapter(this) val adapter = IconTextDialogAdapter(this)
adapter.items = listOf( adapter.items = listOf(
@ -192,7 +190,7 @@ class ExplorerActivity : BaseExplorerActivity() {
"importFromOtherVolumes" -> { "importFromOtherVolumes" -> {
val intent = Intent(this, MainActivity::class.java) val intent = Intent(this, MainActivity::class.java)
intent.action = "pick" intent.action = "pick"
intent.putExtra("sessionID", gocryptfsVolume.sessionID) intent.putExtra("volume", encryptedVolume)
isStartingActivity = true isStartingActivity = true
pickFromOtherVolumes.launch(intent) pickFromOtherVolumes.launch(intent)
} }
@ -215,7 +213,7 @@ class ExplorerActivity : BaseExplorerActivity() {
"camera" -> { "camera" -> {
val intent = Intent(this, CameraActivity::class.java) val intent = Intent(this, CameraActivity::class.java)
intent.putExtra("path", currentDirectoryPath) intent.putExtra("path", currentDirectoryPath)
intent.putExtra("sessionID", gocryptfsVolume.sessionID) intent.putExtra("volume", encryptedVolume)
isStartingActivity = true isStartingActivity = true
startActivity(intent) startActivity(intent)
} }
@ -241,15 +239,15 @@ class ExplorerActivity : BaseExplorerActivity() {
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
} else { } else {
val filePath = PathUtils.pathJoin(currentDirectoryPath, fileName) val filePath = PathUtils.pathJoin(currentDirectoryPath, fileName)
val handleID = gocryptfsVolume.openWriteMode(filePath) //don't check overwrite because openWriteMode open in read-write (doesn't erase content) val handleID = encryptedVolume.openFile(filePath)
if (handleID == -1) { if (handleID == -1L) {
CustomAlertDialogBuilder(this, themeValue) CustomAlertDialogBuilder(this, themeValue)
.setTitle(R.string.error) .setTitle(R.string.error)
.setMessage(R.string.file_creation_failed) .setMessage(R.string.file_creation_failed)
.setPositiveButton(R.string.ok, null) .setPositiveButton(R.string.ok, null)
.show() .show()
} else { } else {
gocryptfsVolume.closeFile(handleID) encryptedVolume.closeFile(handleID)
setCurrentPath(currentDirectoryPath) setCurrentPath(currentDirectoryPath)
invalidateOptionsMenu() invalidateOptionsMenu()
} }
@ -312,7 +310,7 @@ class ExplorerActivity : BaseExplorerActivity() {
for (i in explorerAdapter.selectedItems){ for (i in explorerAdapter.selectedItems){
itemsToProcess.add(OperationFile.fromExplorerElement(explorerElements[i])) itemsToProcess.add(OperationFile.fromExplorerElement(explorerElements[i]))
if (explorerElements[i].isDirectory){ if (explorerElements[i].isDirectory){
gocryptfsVolume.recursiveMapFiles(explorerElements[i].fullPath).forEach { encryptedVolume.recursiveMapFiles(explorerElements[i].fullPath)?.forEach {
itemsToProcess.add(OperationFile.fromExplorerElement(it)) itemsToProcess.add(OperationFile.fromExplorerElement(it))
} }
} }
@ -346,11 +344,11 @@ class ExplorerActivity : BaseExplorerActivity() {
} }
} else if (currentItemAction == ItemsActions.MOVE){ } else if (currentItemAction == ItemsActions.MOVE){
itemsToProcess.forEach { itemsToProcess.forEach {
it.dstPath = PathUtils.pathJoin(currentDirectoryPath, it.explorerElement.name) it.dstPath = PathUtils.pathJoin(currentDirectoryPath, it.name)
it.overwriteConfirmed = false // reset the field in case of a previous cancelled move it.overwriteConfirmed = false // reset the field in case of a previous cancelled move
} }
val toMove = ArrayList<OperationFile>(itemsToProcess.size) val toMove = ArrayList<OperationFile>(itemsToProcess.size)
val toClean = ArrayList<ExplorerElement>() val toClean = ArrayList<String>()
prepareFilesForMove( prepareFilesForMove(
itemsToProcess, itemsToProcess,
toMove, toMove,
@ -398,7 +396,7 @@ class ExplorerActivity : BaseExplorerActivity() {
paths.add(explorerElements[i].fullPath) paths.add(explorerElements[i].fullPath)
} }
isStartingActivity = true isStartingActivity = true
ExternalProvider.share(this, themeValue, gocryptfsVolume, paths) ExternalProvider.share(this, themeValue, encryptedVolume, paths)
unselectAll() unselectAll()
true true
} }
@ -418,12 +416,12 @@ class ExplorerActivity : BaseExplorerActivity() {
*/ */
private fun checkMoveOverwrite(items: List<OperationFile>, callback: (List<OperationFile>?) -> Unit) { private fun checkMoveOverwrite(items: List<OperationFile>, callback: (List<OperationFile>?) -> Unit) {
for (item in items) { for (item in items) {
if (gocryptfsVolume.pathExists(item.dstPath!!) && !item.overwriteConfirmed) { if (encryptedVolume.pathExists(item.dstPath!!) && !item.overwriteConfirmed) {
CustomAlertDialogBuilder(this, themeValue) CustomAlertDialogBuilder(this, themeValue)
.setTitle(R.string.warning) .setTitle(R.string.warning)
.setMessage( .setMessage(
getString( getString(
if (item.explorerElement.isDirectory) { if (item.isDirectory) {
R.string.dir_overwrite_question R.string.dir_overwrite_question
} else { } else {
R.string.file_overwrite_question R.string.file_overwrite_question
@ -440,7 +438,7 @@ class ExplorerActivity : BaseExplorerActivity() {
item.dstPath = PathUtils.pathJoin(PathUtils.getParentPath(item.dstPath!!), it) item.dstPath = PathUtils.pathJoin(PathUtils.getParentPath(item.dstPath!!), it)
checkMoveOverwrite(items, callback) checkMoveOverwrite(items, callback)
}) { }) {
setSelectedText(item.explorerElement.name) setSelectedText(item.name)
show() show()
} }
} }
@ -463,24 +461,24 @@ class ExplorerActivity : BaseExplorerActivity() {
private fun prepareFilesForMove( private fun prepareFilesForMove(
items: List<OperationFile>, items: List<OperationFile>,
toMove: ArrayList<OperationFile>, toMove: ArrayList<OperationFile>,
toClean: ArrayList<ExplorerElement>, toClean: ArrayList<String>,
onReady: () -> Unit onReady: () -> Unit
) { ) {
checkMoveOverwrite(items) { checkedItems -> checkMoveOverwrite(items) { checkedItems ->
checkedItems?.let { checkedItems?.let {
for (item in checkedItems) { for (item in checkedItems) {
if (!item.overwriteConfirmed || !item.explorerElement.isDirectory) { if (!item.overwriteConfirmed || !item.isDirectory) {
toMove.add(item) toMove.add(item)
} }
} }
val toCheck = mutableListOf<OperationFile>() val toCheck = mutableListOf<OperationFile>()
for (item in checkedItems) { for (item in checkedItems) {
if (item.overwriteConfirmed && item.explorerElement.isDirectory) { if (item.overwriteConfirmed && item.isDirectory) {
val children = gocryptfsVolume.listDir(item.explorerElement.fullPath) val children = encryptedVolume.readDir(item.srcPath)
toCheck.addAll(children.map { children?.map {
OperationFile(it, PathUtils.pathJoin(item.dstPath!!, it.name)) OperationFile(it.fullPath, it.stat.type, PathUtils.pathJoin(item.dstPath!!, it.name))
}) }?.let { toCheck.addAll(it) }
toClean.add(item.explorerElement) toClean.add(item.srcPath)
} }
} }
if (toCheck.isEmpty()) { if (toCheck.isEmpty()) {
@ -514,10 +512,10 @@ class ExplorerActivity : BaseExplorerActivity() {
val element = explorerAdapter.explorerElements[i] val element = explorerAdapter.explorerElements[i]
val fullPath = PathUtils.pathJoin(currentDirectoryPath, element.name) val fullPath = PathUtils.pathJoin(currentDirectoryPath, element.name)
if (element.isDirectory) { if (element.isDirectory) {
val result = gocryptfsVolume.recursiveRemoveDirectory(fullPath) val result = encryptedVolume.recursiveRemoveDirectory(fullPath)
result?.let{ failedItem = it } result?.let{ failedItem = it }
} else { } else {
if (!gocryptfsVolume.removeFile(fullPath)) { if (!encryptedVolume.deleteFile(fullPath)) {
failedItem = fullPath failedItem = fullPath
} }
} }

View File

@ -2,10 +2,12 @@ package sushi.hardcore.droidfs.explorers
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.GocryptfsVolume import sushi.hardcore.droidfs.GocryptfsVolume
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
import java.util.* import java.util.*
@ -14,7 +16,7 @@ class ExplorerActivityPick : BaseExplorerActivity() {
private var isFinishingIntentionally = false private var isFinishingIntentionally = false
override fun init() { override fun init() {
super.init() super.init()
resultIntent.putExtra("sessionID", gocryptfsVolume.sessionID) resultIntent.putExtra("volume", encryptedVolume)
} }
override fun bindFileOperationService() { override fun bindFileOperationService() {
@ -65,7 +67,7 @@ class ExplorerActivityPick : BaseExplorerActivity() {
for (i in explorerAdapter.selectedItems) { for (i in explorerAdapter.selectedItems) {
val e = explorerElements[i] val e = explorerElements[i]
paths.add(PathUtils.pathJoin(currentDirectoryPath, e.name)) paths.add(PathUtils.pathJoin(currentDirectoryPath, e.name))
types.add(e.elementType.toInt()) types.add(e.stat.type)
} }
resultIntent.putStringArrayListExtra("paths", paths) resultIntent.putStringArrayListExtra("paths", paths)
resultIntent.putIntegerArrayListExtra("types", types) resultIntent.putIntegerArrayListExtra("types", types)
@ -84,10 +86,7 @@ class ExplorerActivityPick : BaseExplorerActivity() {
override fun closeVolumeOnDestroy() { override fun closeVolumeOnDestroy() {
if (!isFinishingIntentionally && !usf_keep_open){ if (!isFinishingIntentionally && !usf_keep_open){
val sessionID = intent.getIntExtra("originalSessionID", -1) intent.getParcelableExtra<EncryptedVolume>("destinationVolume")?.let { it.close() }
if (sessionID != -1){
GocryptfsVolume(applicationContext, sessionID).close()
}
super.closeVolumeOnDestroy() super.closeVolumeOnDestroy()
} }
} }

View File

@ -1,33 +1,31 @@
package sushi.hardcore.droidfs.explorers package sushi.hardcore.droidfs.explorers
import sushi.hardcore.droidfs.collation.getCollationKeyForFileName import sushi.hardcore.droidfs.collation.getCollationKeyForFileName
import sushi.hardcore.droidfs.filesystems.Stat
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
import java.text.Collator import java.text.Collator
import java.util.*
class ExplorerElement(val name: String, val elementType: Short, var size: Long = -1, mTime: Long = -1, val parentPath: String) { class ExplorerElement(val name: String, val stat: Stat, val parentPath: String) {
val mTime = Date((mTime * 1000).toString().toLong())
val fullPath: String = PathUtils.pathJoin(parentPath, name) val fullPath: String = PathUtils.pathJoin(parentPath, name)
val collationKey = Collator.getInstance().getCollationKeyForFileName(fullPath) val collationKey = Collator.getInstance().getCollationKeyForFileName(fullPath)
val isDirectory: Boolean val isDirectory: Boolean
get() = elementType.toInt() == DIRECTORY_TYPE get() = stat.type == Stat.S_IFDIR
val isParentFolder: Boolean
get() = elementType.toInt() == PARENT_FOLDER_TYPE
val isRegularFile: Boolean val isRegularFile: Boolean
get() = elementType.toInt() == REGULAR_FILE_TYPE get() = stat.type == Stat.S_IFREG
val isSymlink: Boolean
get() = stat.type == Stat.S_IFLNK
val isParentFolder: Boolean
get() = stat.type == Stat.PARENT_FOLDER_TYPE
companion object { companion object {
const val DIRECTORY_TYPE = 0
const val PARENT_FOLDER_TYPE = -1
const val REGULAR_FILE_TYPE = 1
@JvmStatic @JvmStatic
//this function is needed because I had some problems calling the constructor from JNI, probably due to arguments with default values //this function is needed because I had some problems calling the constructor from JNI, probably due to arguments with default values
fun new(name: String, elementType: Short, size: Long, mTime: Long, parentPath: String): ExplorerElement { fun new(name: String, elementType: Int, size: Long, mTime: Long, parentPath: String): ExplorerElement {
return ExplorerElement(name, elementType, size, mTime, parentPath) return ExplorerElement(name, Stat(elementType, size, mTime*1000), parentPath)
} }
private fun foldersFirst(a: ExplorerElement, b: ExplorerElement, default: () -> Int): Int { private fun foldersFirst(a: ExplorerElement, b: ExplorerElement, default: () -> Int): Int {
@ -61,12 +59,12 @@ class ExplorerElement(val name: String, val elementType: Short, var size: Long =
} }
"size" -> { "size" -> {
explorerElements.sortWith { a, b -> explorerElements.sortWith { a, b ->
doSort(a, b, foldersFirst) { (a.size - b.size).toInt() } doSort(a, b, foldersFirst) { (a.stat.size - b.stat.size).toInt() }
} }
} }
"date" -> { "date" -> {
explorerElements.sortWith { a, b -> explorerElements.sortWith { a, b ->
doSort(a, b, foldersFirst) { a.mTime.compareTo(b.mTime) } doSort(a, b, foldersFirst) { a.stat.mTime.compareTo(b.stat.mTime) }
} }
} }
"name_desc" -> { "name_desc" -> {
@ -76,12 +74,12 @@ class ExplorerElement(val name: String, val elementType: Short, var size: Long =
} }
"size_desc" -> { "size_desc" -> {
explorerElements.sortWith { a, b -> explorerElements.sortWith { a, b ->
doSort(a, b, foldersFirst) { (b.size - a.size).toInt() } doSort(a, b, foldersFirst) { (b.stat.size - a.stat.size).toInt() }
} }
} }
"date_desc" -> { "date_desc" -> {
explorerElements.sortWith { a, b -> explorerElements.sortWith { a, b ->
doSort(a, b, foldersFirst) { b.mTime.compareTo(a.mTime) } doSort(a, b, foldersFirst) { b.stat.mTime.compareTo(a.stat.mTime) }
} }
} }
} }

View File

@ -17,6 +17,7 @@ import kotlinx.coroutines.*
import sushi.hardcore.droidfs.GocryptfsVolume import sushi.hardcore.droidfs.GocryptfsVolume
import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.explorers.ExplorerElement import sushi.hardcore.droidfs.explorers.ExplorerElement
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.util.Wiper import sushi.hardcore.droidfs.util.Wiper
import java.io.File import java.io.File
@ -29,15 +30,15 @@ class FileOperationService : Service() {
} }
private val binder = LocalBinder() private val binder = LocalBinder()
private lateinit var gocryptfsVolume: GocryptfsVolume private lateinit var encryptedVolume: EncryptedVolume
private lateinit var notificationManager: NotificationManagerCompat private lateinit var notificationManager: NotificationManagerCompat
private val tasks = HashMap<Int, Job>() private val tasks = HashMap<Int, Job>()
private var lastNotificationId = 0 private var lastNotificationId = 0
inner class LocalBinder : Binder() { inner class LocalBinder : Binder() {
fun getService(): FileOperationService = this@FileOperationService fun getService(): FileOperationService = this@FileOperationService
fun setGocryptfsVolume(g: GocryptfsVolume) { fun setEncryptedVolume(volume: EncryptedVolume) {
gocryptfsVolume = g encryptedVolume = volume
} }
} }
@ -121,17 +122,17 @@ class FileOperationService : Service() {
} }
} }
private fun copyFile(srcPath: String, dstPath: String, remoteGocryptfsVolume: GocryptfsVolume = gocryptfsVolume): Boolean { private fun copyFile(srcPath: String, dstPath: String, remoteEncryptedVolume: EncryptedVolume = encryptedVolume): Boolean {
var success = true var success = true
val srcHandleId = remoteGocryptfsVolume.openReadMode(srcPath) val srcFileHandle = remoteEncryptedVolume.openFile(srcPath)
if (srcHandleId != -1){ if (srcFileHandle != -1L) {
val dstHandleId = gocryptfsVolume.openWriteMode(dstPath) val dstFileHandle = encryptedVolume.openFile(dstPath)
if (dstHandleId != -1){ if (dstFileHandle != -1L) {
var offset: Long = 0 var offset: Long = 0
val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS) val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS)
var length: Int var length: Int
while (remoteGocryptfsVolume.readFile(srcHandleId, offset, ioBuffer).also { length = it } > 0) { while (remoteEncryptedVolume.read(srcFileHandle, ioBuffer, offset).also { length = it } > 0) {
val written = gocryptfsVolume.writeFile(dstHandleId, offset, ioBuffer, length).toLong() val written = encryptedVolume.write(dstFileHandle, offset, ioBuffer, length).toLong()
if (written == length.toLong()) { if (written == length.toLong()) {
offset += written offset += written
} else { } else {
@ -139,11 +140,11 @@ class FileOperationService : Service() {
break break
} }
} }
gocryptfsVolume.closeFile(dstHandleId) encryptedVolume.closeFile(dstFileHandle)
} else { } else {
success = false success = false
} }
remoteGocryptfsVolume.closeFile(srcHandleId) remoteEncryptedVolume.closeFile(srcFileHandle)
} else { } else {
success = false success = false
} }
@ -152,22 +153,22 @@ class FileOperationService : Service() {
suspend fun copyElements( suspend fun copyElements(
items: ArrayList<OperationFile>, items: ArrayList<OperationFile>,
remoteGocryptfsVolume: GocryptfsVolume = gocryptfsVolume remoteEncryptedVolume: EncryptedVolume = encryptedVolume
): String? = coroutineScope { ): String? = coroutineScope {
val notification = showNotification(R.string.file_op_copy_msg, items.size) val notification = showNotification(R.string.file_op_copy_msg, items.size)
val task = async { val task = async {
var failedItem: String? = null var failedItem: String? = null
for (i in 0 until items.size) { for (i in 0 until items.size) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
if (items[i].explorerElement.isDirectory) { if (items[i].isDirectory) {
if (!gocryptfsVolume.pathExists(items[i].dstPath!!)) { if (!encryptedVolume.pathExists(items[i].dstPath!!)) {
if (!gocryptfsVolume.mkdir(items[i].dstPath!!)) { if (!encryptedVolume.mkdir(items[i].dstPath!!)) {
failedItem = items[i].explorerElement.fullPath failedItem = items[i].srcPath
} }
} }
} else { } else {
if (!copyFile(items[i].explorerElement.fullPath, items[i].dstPath!!, remoteGocryptfsVolume)) { if (!copyFile(items[i].srcPath, items[i].dstPath!!, remoteEncryptedVolume)) {
failedItem = items[i].explorerElement.fullPath failedItem = items[i].srcPath
} }
} }
} }
@ -183,23 +184,23 @@ class FileOperationService : Service() {
waitForTask(notification, task).failedItem waitForTask(notification, task).failedItem
} }
suspend fun moveElements(toMove: List<OperationFile>, toClean: List<ExplorerElement>): String? = coroutineScope { suspend fun moveElements(toMove: List<OperationFile>, toClean: List<String>): String? = coroutineScope {
val notification = showNotification(R.string.file_op_move_msg, toMove.size) val notification = showNotification(R.string.file_op_move_msg, toMove.size)
val task = async(Dispatchers.IO) { val task = async(Dispatchers.IO) {
val total = toMove.size+toClean.size val total = toMove.size+toClean.size
var failedItem: String? = null var failedItem: String? = null
for ((i, item) in toMove.withIndex()) { for ((i, item) in toMove.withIndex()) {
if (!gocryptfsVolume.rename(item.explorerElement.fullPath, item.dstPath!!)) { if (!encryptedVolume.rename(item.srcPath, item.dstPath!!)) {
failedItem = item.explorerElement.fullPath failedItem = item.srcPath
break break
} else { } else {
updateNotificationProgress(notification, i+1, total) updateNotificationProgress(notification, i+1, total)
} }
} }
if (failedItem == null) { if (failedItem == null) {
for ((i, folder) in toClean.asReversed().withIndex()) { for ((i, folderPath) in toClean.asReversed().withIndex()) {
if (!gocryptfsVolume.rmdir(folder.fullPath)) { if (!encryptedVolume.rmdir(folderPath)) {
failedItem = folder.fullPath failedItem = folderPath
break break
} else { } else {
updateNotificationProgress(notification, toMove.size+i+1, total) updateNotificationProgress(notification, toMove.size+i+1, total)
@ -221,7 +222,7 @@ class FileOperationService : Service() {
for (i in dstPaths.indices) { for (i in dstPaths.indices) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
try { try {
if (!gocryptfsVolume.importFile(this@FileOperationService, uris[i], dstPaths[i])) { if (!encryptedVolume.importFile(this@FileOperationService, uris[i], dstPaths[i])) {
failedIndex = i failedIndex = i
} }
} catch (e: FileNotFoundException) { } catch (e: FileNotFoundException) {
@ -301,7 +302,7 @@ class FileOperationService : Service() {
// create destination folders so the new files can use them // create destination folders so the new files can use them
for (dir in dstDirs) { for (dir in dstDirs) {
if (!gocryptfsVolume.mkdir(dir)) { if (!encryptedVolume.mkdir(dir)) {
failedItem = dir failedItem = dir
break break
} }
@ -345,7 +346,7 @@ class FileOperationService : Service() {
return if (outputStream == null) { return if (outputStream == null) {
false false
} else { } else {
gocryptfsVolume.exportFile(srcPath, outputStream) encryptedVolume.exportFile(srcPath, outputStream)
} }
} }
@ -355,7 +356,7 @@ class FileOperationService : Service() {
scope: CoroutineScope scope: CoroutineScope
): String? { ): String? {
treeDocumentFile.createDirectory(File(plain_directory_path).name)?.let { childTree -> treeDocumentFile.createDirectory(File(plain_directory_path).name)?.let { childTree ->
val explorerElements = gocryptfsVolume.listDir(plain_directory_path) val explorerElements = encryptedVolume.readDir(plain_directory_path) ?: return null
for (e in explorerElements) { for (e in explorerElements) {
if (!scope.isActive) { if (!scope.isActive) {
return null return null

View File

@ -1,11 +1,22 @@
package sushi.hardcore.droidfs.file_operations package sushi.hardcore.droidfs.file_operations
import sushi.hardcore.droidfs.explorers.ExplorerElement import sushi.hardcore.droidfs.explorers.ExplorerElement
import sushi.hardcore.droidfs.filesystems.Stat
import sushi.hardcore.droidfs.util.PathUtils
import java.io.File
class OperationFile(val srcPath: String, val type: Int, var dstPath: String? = null, var overwriteConfirmed: Boolean = false) {
val isDirectory = type == Stat.S_IFDIR
val name: String by lazy {
File(srcPath).name
}
val parentPath by lazy {
PathUtils.getParentPath(srcPath)
}
class OperationFile(val explorerElement: ExplorerElement, var dstPath: String? = null, var overwriteConfirmed: Boolean = false) {
companion object { companion object {
fun fromExplorerElement(e: ExplorerElement): OperationFile { fun fromExplorerElement(e: ExplorerElement): OperationFile {
return OperationFile(e, null) return OperationFile(e.fullPath, e.stat.type)
} }
} }
} }

View File

@ -5,18 +5,18 @@ import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DataSpec import com.google.android.exoplayer2.upstream.DataSpec
import com.google.android.exoplayer2.upstream.TransferListener import com.google.android.exoplayer2.upstream.TransferListener
import sushi.hardcore.droidfs.ConstValues import sushi.hardcore.droidfs.ConstValues
import sushi.hardcore.droidfs.GocryptfsVolume import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import kotlin.math.ceil import kotlin.math.ceil
import kotlin.math.min import kotlin.math.min
class GocryptfsDataSource(private val gocryptfsVolume: GocryptfsVolume, private val filePath: String): DataSource { class EncryptedVolumeDataSource(private val encryptedVolume: EncryptedVolume, private val filePath: String): DataSource {
private var handleID = -1 private var fileHandle = -1L
private var fileSize: Long = -1 private var fileSize: Long = -1
private var fileOffset: Long = 0 private var fileOffset: Long = 0
override fun open(dataSpec: DataSpec): Long { override fun open(dataSpec: DataSpec): Long {
fileOffset = dataSpec.position fileOffset = dataSpec.position
handleID = gocryptfsVolume.openReadMode(filePath) fileHandle = encryptedVolume.openFile(filePath)
fileSize = gocryptfsVolume.getSize(filePath) fileSize = encryptedVolume.getAttr(filePath)!!.size
return fileSize return fileSize
} }
@ -25,7 +25,7 @@ class GocryptfsDataSource(private val gocryptfsVolume: GocryptfsVolume, private
} }
override fun close() { override fun close() {
gocryptfsVolume.closeFile(handleID) encryptedVolume.closeFile(fileHandle)
} }
override fun addTransferListener(transferListener: TransferListener) { override fun addTransferListener(transferListener: TransferListener) {
@ -44,7 +44,7 @@ class GocryptfsDataSource(private val gocryptfsVolume: GocryptfsVolume, private
} else { } else {
ByteArray(tmpReadLength) ByteArray(tmpReadLength)
} }
val read = gocryptfsVolume.readFile(handleID, fileOffset, tmpBuff) val read = encryptedVolume.read(fileHandle, tmpBuff, fileOffset)
System.arraycopy(tmpBuff, 0, buffer, offset+totalRead, read) System.arraycopy(tmpBuff, 0, buffer, offset+totalRead, read)
fileOffset += read fileOffset += read
totalRead += read totalRead += read
@ -52,9 +52,9 @@ class GocryptfsDataSource(private val gocryptfsVolume: GocryptfsVolume, private
return totalRead return totalRead
} }
class Factory(private val gocryptfsVolume: GocryptfsVolume, private val filePath: String): DataSource.Factory { class Factory(private val encryptedVolume: EncryptedVolume, private val filePath: String): DataSource.Factory {
override fun createDataSource(): DataSource { override fun createDataSource(): DataSource {
return GocryptfsDataSource(gocryptfsVolume, filePath) return EncryptedVolumeDataSource(encryptedVolume, filePath)
} }
} }
} }

View File

@ -10,11 +10,12 @@ import sushi.hardcore.droidfs.GocryptfsVolume
import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
import sushi.hardcore.droidfs.explorers.ExplorerElement import sushi.hardcore.droidfs.explorers.ExplorerElement
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
abstract class FileViewerActivity: BaseActivity() { abstract class FileViewerActivity: BaseActivity() {
protected lateinit var gocryptfsVolume: GocryptfsVolume protected lateinit var encryptedVolume: EncryptedVolume
protected lateinit var filePath: String protected lateinit var filePath: String
private lateinit var originalParentPath: String private lateinit var originalParentPath: String
private lateinit var windowInsetsController: WindowInsetsControllerCompat private lateinit var windowInsetsController: WindowInsetsControllerCompat
@ -33,8 +34,7 @@ abstract class FileViewerActivity: BaseActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
filePath = intent.getStringExtra("path")!! filePath = intent.getStringExtra("path")!!
originalParentPath = PathUtils.getParentPath(filePath) originalParentPath = PathUtils.getParentPath(filePath)
val sessionID = intent.getIntExtra("sessionID", -1) encryptedVolume = intent.getParcelableExtra("volume")!!
gocryptfsVolume = GocryptfsVolume(applicationContext, sessionID)
usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false) usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false)
foldersFirst = sharedPrefs.getBoolean("folders_first", true) foldersFirst = sharedPrefs.getBoolean("folders_first", true)
windowInsetsController = WindowInsetsControllerCompat(window, window.decorView) windowInsetsController = WindowInsetsControllerCompat(window, window.decorView)
@ -67,7 +67,7 @@ abstract class FileViewerActivity: BaseActivity() {
} }
protected fun loadWholeFile(path: String, fileSize: Long? = null): ByteArray? { protected fun loadWholeFile(path: String, fileSize: Long? = null): ByteArray? {
val result = gocryptfsVolume.loadWholeFile(path, size = fileSize) val result = encryptedVolume.loadWholeFile(path, size = fileSize)
if (result.second != 0) { if (result.second != 0) {
val dialog = CustomAlertDialogBuilder(this, themeValue) val dialog = CustomAlertDialogBuilder(this, themeValue)
.setTitle(R.string.error) .setTitle(R.string.error)
@ -85,10 +85,12 @@ abstract class FileViewerActivity: BaseActivity() {
protected fun createPlaylist() { protected fun createPlaylist() {
if (!wasMapped){ if (!wasMapped){
for (e in gocryptfsVolume.recursiveMapFiles(originalParentPath)) { encryptedVolume.recursiveMapFiles(originalParentPath)?.let { elements ->
if (e.isRegularFile) { for (e in elements) {
if (ConstValues.isExtensionType(getFileType(), e.name) || filePath == e.fullPath) { if (e.isRegularFile) {
mappedPlaylist.add(e) if (ConstValues.isExtensionType(getFileType(), e.name) || filePath == e.fullPath) {
mappedPlaylist.add(e)
}
} }
} }
} }
@ -133,7 +135,7 @@ abstract class FileViewerActivity: BaseActivity() {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
if (!isFinishingIntentionally) { if (!isFinishingIntentionally) {
gocryptfsVolume.close() encryptedVolume.close()
RestrictedFileProvider.wipeAll(this) RestrictedFileProvider.wipeAll(this)
} }
} }

View File

@ -99,7 +99,7 @@ class ImageViewer: FileViewerActivity() {
.setTitle(R.string.warning) .setTitle(R.string.warning)
.setPositiveButton(R.string.ok) { _, _ -> .setPositiveButton(R.string.ok) { _, _ ->
createPlaylist() //be sure the playlist is created before deleting if there is only one image createPlaylist() //be sure the playlist is created before deleting if there is only one image
if (gocryptfsVolume.removeFile(filePath)) { if (encryptedVolume.deleteFile(filePath)) {
playlistNext(true) playlistNext(true)
refreshPlaylist() refreshPlaylist()
if (mappedPlaylist.size == 0) { //deleted all images of the playlist if (mappedPlaylist.size == 0) { //deleted all images of the playlist
@ -275,7 +275,7 @@ class ImageViewer: FileViewerActivity() {
Bitmap.CompressFormat.JPEG Bitmap.CompressFormat.JPEG
}, 100, outputStream) == true }, 100, outputStream) == true
){ ){
if (gocryptfsVolume.importFile(ByteArrayInputStream(outputStream.toByteArray()), filePath)){ if (encryptedVolume.importFile(ByteArrayInputStream(outputStream.toByteArray()), filePath)){
Toast.makeText(this, R.string.image_saved_successfully, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.image_saved_successfully, Toast.LENGTH_SHORT).show()
callback() callback()
} else { } else {

View File

@ -25,7 +25,7 @@ abstract class MediaPlayer: FileViewerActivity() {
protected open fun onVideoSizeChanged(width: Int, height: Int) {} protected open fun onVideoSizeChanged(width: Int, height: Int) {}
private fun createMediaSource(filePath: String): MediaSource { private fun createMediaSource(filePath: String): MediaSource {
val dataSourceFactory = GocryptfsDataSource.Factory(gocryptfsVolume, filePath) val dataSourceFactory = EncryptedVolumeDataSource.Factory(encryptedVolume, filePath)
return ProgressiveMediaSource.Factory(dataSourceFactory, DefaultExtractorsFactory()) return ProgressiveMediaSource.Factory(dataSourceFactory, DefaultExtractorsFactory())
.createMediaSource(MediaItem.fromUri(ConstValues.FAKE_URI)) .createMediaSource(MediaItem.fromUri(ConstValues.FAKE_URI))
} }

View File

@ -21,7 +21,7 @@ class PdfViewer: FileViewerActivity() {
pdfViewer = PdfViewer(this) pdfViewer = PdfViewer(this)
val fileName = File(filePath).name val fileName = File(filePath).name
title = fileName title = fileName
val fileSize = gocryptfsVolume.getSize(filePath) val fileSize = encryptedVolume.getAttr(filePath)?.size
loadWholeFile(filePath, fileSize)?.let { loadWholeFile(filePath, fileSize)?.let {
pdfViewer.loadPdf(ByteArrayInputStream(it), fileName, fileSize) pdfViewer.loadPdf(ByteArrayInputStream(it), fileName, fileSize)
} }

View File

@ -3,6 +3,7 @@ package sushi.hardcore.droidfs.file_viewers
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.widget.EditText import android.widget.EditText
@ -68,14 +69,14 @@ class TextEditor: FileViewerActivity() {
private fun save(): Boolean{ private fun save(): Boolean{
var success = false var success = false
val content = editor.text.toString().toByteArray() val content = editor.text.toString().toByteArray()
val handleID = gocryptfsVolume.openWriteMode(filePath) val fileHandle = encryptedVolume.openFile(filePath)
if (handleID != -1){ if (fileHandle != -1L) {
val buff = ByteArrayInputStream(content) val buff = ByteArrayInputStream(content)
var offset: Long = 0 var offset: Long = 0
val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS) val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS)
var length: Int var length: Int
while (buff.read(ioBuffer).also { length = it } > 0) { while (buff.read(ioBuffer).also { length = it } > 0) {
val written = gocryptfsVolume.writeFile(handleID, offset, ioBuffer, length).toLong() val written = encryptedVolume.write(fileHandle, offset, ioBuffer, length).toLong()
if (written == length.toLong()) { if (written == length.toLong()) {
offset += written offset += written
} else { } else {
@ -83,9 +84,9 @@ class TextEditor: FileViewerActivity() {
} }
} }
if (offset == content.size.toLong()){ if (offset == content.size.toLong()){
success = gocryptfsVolume.truncate(handleID, offset) success = encryptedVolume.truncate(filePath, offset)
} }
gocryptfsVolume.closeFile(handleID) encryptedVolume.closeFile(fileHandle)
buff.close() buff.close()
} }
if (success){ if (success){

View File

@ -0,0 +1,99 @@
package sushi.hardcore.droidfs.filesystems
import android.os.Parcel
import sushi.hardcore.droidfs.explorers.ExplorerElement
class CryfsVolume(private val fusePtr: Long): EncryptedVolume() {
companion object {
init {
System.loadLibrary("cryfs_jni")
}
const val CONFIG_FILE_NAME = "cryfs.config"
private external fun nativeInit(baseDir: String, localStateDir: String, password: ByteArray): Long
private external fun nativeCreate(fusePtr: Long, path: String, mode: Int): Long
private external fun nativeOpen(fusePtr: Long, path: String, flags: Int): Long
private external fun nativeRead(fusePtr: Long, fileHandle: Long, buffer: ByteArray, offset: Long): Int
private external fun nativeWrite(fusePtr: Long, fileHandle: Long, offset: Long, buffer: ByteArray, size: Int): Int
private external fun nativeTruncate(fusePtr: Long, path: String, size: Long): Boolean
private external fun nativeDeleteFile(fusePtr: Long, path: String): Boolean
private external fun nativeCloseFile(fusePtr: Long, fileHandle: Long): Boolean
private external fun nativeReadDir(fusePtr: Long, path: String): MutableList<ExplorerElement>?
private external fun nativeMkdir(fusePtr: Long, path: String, mode: Int): Boolean
private external fun nativeRmdir(fusePtr: Long, path: String): Boolean
private external fun nativeGetAttr(fusePtr: Long, path: String): Stat?
private external fun nativeRename(fusePtr: Long, srcPath: String, dstPath: String): Boolean
private external fun nativeClose(fusePtr: Long)
private external fun nativeIsClosed(fusePtr: Long): Boolean
fun init(baseDir: String, localStateDir: String, password: ByteArray): CryfsVolume {
val fusePtr = nativeInit(baseDir, localStateDir, password)
return CryfsVolume(fusePtr)
}
}
constructor(parcel: Parcel) : this(parcel.readLong())
override fun writeToParcel(parcel: Parcel, flags: Int) = with(parcel) {
writeByte(CRYFS_VOLUME_TYPE)
writeLong(fusePtr)
}
override fun openFile(path: String): Long {
val fileHandle = nativeOpen(fusePtr, path, 0)
return if (fileHandle == -1L) {
nativeCreate(fusePtr, path, 0)
} else {
fileHandle
}
}
override fun read(fileHandle: Long, buffer: ByteArray, offset: Long): Int {
return nativeRead(fusePtr, fileHandle, buffer, offset)
}
override fun write(fileHandle: Long, offset: Long, buffer: ByteArray, size: Int): Int {
return nativeWrite(fusePtr, fileHandle, offset, buffer, size)
}
override fun truncate(path: String, size: Long): Boolean {
return nativeTruncate(fusePtr, path, size)
}
override fun closeFile(fileHandle: Long): Boolean {
return nativeCloseFile(fusePtr, fileHandle)
}
override fun deleteFile(path: String): Boolean {
return nativeDeleteFile(fusePtr, path)
}
override fun readDir(path: String): MutableList<ExplorerElement>? {
return nativeReadDir(fusePtr, path)
}
override fun mkdir(path: String): Boolean {
return nativeMkdir(fusePtr, path, Stat.S_IFDIR)
}
override fun rmdir(path: String): Boolean {
return nativeRmdir(fusePtr, path)
}
override fun getAttr(path: String): Stat? {
return nativeGetAttr(fusePtr, path)
}
override fun rename(srcPath: String, dstPath: String): Boolean {
return nativeRename(fusePtr, srcPath, dstPath)
}
override fun close() {
return nativeClose(fusePtr)
}
override fun isClosed(): Boolean {
return nativeIsClosed(fusePtr)
}
}

View File

@ -0,0 +1,214 @@
package sushi.hardcore.droidfs.filesystems
import android.content.Context
import android.net.Uri
import android.os.Parcel
import android.os.Parcelable
import sushi.hardcore.droidfs.GocryptfsVolume
import sushi.hardcore.droidfs.SavedVolume
import sushi.hardcore.droidfs.explorers.ExplorerElement
import sushi.hardcore.droidfs.util.PathUtils
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
abstract class EncryptedVolume: Parcelable {
companion object {
const val GOCRYPTFS_VOLUME_TYPE: Byte = 0
const val CRYFS_VOLUME_TYPE: Byte = 1
@JvmField
val CREATOR = object : Parcelable.Creator<EncryptedVolume> {
override fun createFromParcel(parcel: Parcel): EncryptedVolume {
return when (parcel.readByte()) {
GOCRYPTFS_VOLUME_TYPE -> GocryptfsVolume(parcel)
CRYFS_VOLUME_TYPE -> CryfsVolume(parcel)
else -> throw invalidVolumeType()
}
}
override fun newArray(size: Int) = arrayOfNulls<EncryptedVolume>(size)
}
fun getVolumeType(path: String): Byte {
return if (File(path, GocryptfsVolume.CONFIG_FILE_NAME).isFile) {
GOCRYPTFS_VOLUME_TYPE
} else if (File(path, CryfsVolume.CONFIG_FILE_NAME).isFile) {
CRYFS_VOLUME_TYPE
} else {
-1
}
}
fun init(volume: SavedVolume, filesDir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ByteArray?): EncryptedVolume? {
return when (volume.type) {
GOCRYPTFS_VOLUME_TYPE -> {
GocryptfsVolume.init(volume.getFullPath(filesDir), password, givenHash, returnedHash)
}
CRYFS_VOLUME_TYPE -> {
CryfsVolume.init(volume.getFullPath(filesDir), PathUtils.pathJoin(filesDir, "localState"), password!!)
}
else -> throw invalidVolumeType()
}
}
private fun invalidVolumeType(): java.lang.RuntimeException {
return RuntimeException("Invalid volume type")
}
}
override fun describeContents() = 0
abstract fun openFile(path: String): Long
abstract fun read(fileHandle: Long, buffer: ByteArray, offset: Long): Int
abstract fun write(fileHandle: Long, offset: Long, buffer: ByteArray, size: Int): Int
abstract fun closeFile(fileHandle: Long): Boolean
abstract fun truncate(path: String, size: Long): Boolean
abstract fun deleteFile(path: String): Boolean
abstract fun readDir(path: String): MutableList<ExplorerElement>?
abstract fun mkdir(path: String): Boolean
abstract fun rmdir(path: String): Boolean
abstract fun getAttr(path: String): Stat?
abstract fun rename(srcPath: String, dstPath: String): Boolean
abstract fun close()
abstract fun isClosed(): Boolean
fun pathExists(path: String): Boolean {
return getAttr(path) != null
}
fun exportFile(fileHandle: Long, os: OutputStream): Boolean {
var offset: Long = 0
val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS)
var length: Int
while (read(fileHandle, ioBuffer, offset).also { length = it } > 0){
os.write(ioBuffer, 0, length)
offset += length.toLong()
}
os.close()
return true
}
fun exportFile(src_path: String, os: OutputStream): Boolean {
var success = false
val srcfileHandle = openFile(src_path)
if (srcfileHandle != -1L) {
success = exportFile(srcfileHandle, os)
closeFile(srcfileHandle)
}
return success
}
fun exportFile(src_path: String, dst_path: String): Boolean {
return exportFile(src_path, FileOutputStream(dst_path))
}
fun exportFile(context: Context, src_path: String, output_path: Uri): Boolean {
val os = context.contentResolver.openOutputStream(output_path)
if (os != null){
return exportFile(src_path, os)
}
return false
}
fun importFile(inputStream: InputStream, dst_path: String): Boolean {
val dstfileHandle = openFile(dst_path)
if (dstfileHandle != -1L) {
var success = true
var offset: Long = 0
val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS)
var length: Int
while (inputStream.read(ioBuffer).also { length = it } > 0) {
val written = write(dstfileHandle, offset, ioBuffer, length).toLong()
if (written == length.toLong()) {
offset += written
} else {
inputStream.close()
success = false
break
}
}
closeFile(dstfileHandle)
inputStream.close()
return success
}
return false
}
fun importFile(context: Context, src_uri: Uri, dst_path: String): Boolean {
val inputStream = context.contentResolver.openInputStream(src_uri)
if (inputStream != null){
return importFile(inputStream, dst_path)
}
return false
}
fun loadWholeFile(fullPath: String, size: Long? = null, maxSize: Long? = null): Pair<ByteArray?, Int> {
val fileSize = size ?: getAttr(fullPath)?.size ?: -1
return if (fileSize >= 0) {
maxSize?.let {
if (fileSize > it) {
return Pair(null, 0)
}
}
try {
val fileBuff = ByteArray(fileSize.toInt())
val fileHandle = openFile(fullPath)
if (fileHandle == -1L) {
Pair(null, 3)
} else {
var offset: Long = 0
val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS)
var length: Int
while (read(fileHandle, ioBuffer, offset).also { length = it } > 0) {
System.arraycopy(ioBuffer, 0, fileBuff, offset.toInt(), length)
offset += length.toLong()
}
closeFile(fileHandle)
if (offset == fileBuff.size.toLong()) {
Pair(fileBuff, 0)
} else {
Pair(null, 4)
}
}
} catch (e: OutOfMemoryError) {
Pair(null, 2)
}
} else {
Pair(null, 1)
}
}
fun recursiveMapFiles(rootPath: String): MutableList<ExplorerElement>? {
val result = mutableListOf<ExplorerElement>()
val explorerElements = readDir(rootPath) ?: return null
result.addAll(explorerElements)
for (e in explorerElements) {
if (e.isDirectory) {
result.addAll(recursiveMapFiles(e.fullPath) ?: return null)
}
}
return result
}
fun recursiveRemoveDirectory(path: String): String? {
readDir(path)?.let { elements ->
for (e in elements) {
val fullPath = PathUtils.pathJoin(path, e.name)
if (e.isDirectory) {
val result = recursiveRemoveDirectory(fullPath)
result?.let { return it }
} else {
if (!deleteFile(fullPath)) {
return fullPath
}
}
}
}
return if (!rmdir(path)) {
path
} else {
null
}
}
}

View File

@ -0,0 +1,14 @@
package sushi.hardcore.droidfs.filesystems
class Stat(val type: Int, var size: Long, val mTime: Long) {
companion object {
const val S_IFDIR = 0x4000
const val S_IFREG = 0x8000
const val S_IFLNK = 0xA000
const val PARENT_FOLDER_TYPE = -1
fun parentFolderStat(): Stat {
return Stat(PARENT_FOLDER_TYPE, -1, -1)
}
}
}

View File

@ -6,6 +6,7 @@ import android.net.Uri
import android.os.storage.StorageManager import android.os.storage.StorageManager
import android.provider.DocumentsContract import android.provider.DocumentsContract
import android.provider.OpenableColumns import android.provider.OpenableColumns
import android.util.Log
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.R
@ -19,16 +20,16 @@ object PathUtils {
fun getParentPath(path: String): String { fun getParentPath(path: String): String {
return if (path.endsWith("/")) { return if (path.endsWith("/")) {
val a = path.substring(0, path.length - 2) val a = path.substring(0, path.length - 2)
if (a.contains("/")) { if (a.count { it == '/' } == 1) {
a.substring(0, a.lastIndexOf("/")) "/"
} else { } else {
"" a.substring(0, a.lastIndexOf("/"))
} }
} else { } else {
if (path.contains("/")) { if (path.count { it == '/' } == 1) {
path.substring(0, path.lastIndexOf("/")) "/"
} else { } else {
"" path.substring(0, path.lastIndexOf("/"))
} }
} }
} }

View File

@ -78,16 +78,15 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_createVolume(JNIEnv *
} }
JNIEXPORT jint JNICALL JNIEXPORT jint JNICALL
Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_init(JNIEnv *env, jobject clazz, Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_nativeInit(JNIEnv *env, jobject clazz,
jstring jroot_cipher_dir, jstring jroot_cipher_dir,
jcharArray jpassword, jbyteArray jpassword,
jbyteArray jgiven_hash, jbyteArray jgiven_hash,
jbyteArray jreturned_hash) { jbyteArray jreturned_hash) {
const char* root_cipher_dir = (*env)->GetStringUTFChars(env, jroot_cipher_dir, NULL); const char* root_cipher_dir = (*env)->GetStringUTFChars(env, jroot_cipher_dir, NULL);
GoString go_root_cipher_dir = {root_cipher_dir, strlen(root_cipher_dir)}; GoString go_root_cipher_dir = {root_cipher_dir, strlen(root_cipher_dir)};
size_t password_len; size_t password_len;
jchar* jchar_password;
char* password; char* password;
GoSlice go_password = {NULL, 0, 0}; GoSlice go_password = {NULL, 0, 0};
size_t given_hash_len; size_t given_hash_len;
@ -96,9 +95,7 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_init(JNIEnv *env, job
GoSlice go_given_hash = {NULL, 0, 0}; GoSlice go_given_hash = {NULL, 0, 0};
if ((*env)->IsSameObject(env, jgiven_hash, NULL)){ if ((*env)->IsSameObject(env, jgiven_hash, NULL)){
password_len = (*env)->GetArrayLength(env, jpassword); password_len = (*env)->GetArrayLength(env, jpassword);
jchar_password = (*env)->GetCharArrayElements(env, jpassword, NULL); password = (char*)(*env)->GetByteArrayElements(env, jpassword, NULL);
password = malloc(password_len);
jcharArray_to_charArray(jchar_password, password, password_len);
go_password.data = password; go_password.data = password;
go_password.len = password_len; go_password.len = password_len;
go_password.cap = password_len; go_password.cap = password_len;
@ -131,8 +128,7 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_init(JNIEnv *env, job
if ((*env)->IsSameObject(env, jgiven_hash, NULL)){ if ((*env)->IsSameObject(env, jgiven_hash, NULL)){
wipe(password, password_len); wipe(password, password_len);
free(password); (*env)->ReleaseByteArrayElements(env, jpassword, (jbyte*)password, 0);
(*env)->ReleaseCharArrayElements(env, jpassword, jchar_password, 0);
} else { } else {
wipe(given_hash, given_hash_len); wipe(given_hash, given_hash_len);
free(given_hash); free(given_hash);
@ -256,7 +252,7 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1list_1dir(JNIEnv *env, jobje
jmethodID java_ArrayList_add = (*env)->GetMethodID(env, java_ArrayList, "add", "(Ljava/lang/Object;)Z"); jmethodID java_ArrayList_add = (*env)->GetMethodID(env, java_ArrayList, "add", "(Ljava/lang/Object;)Z");
jclass classExplorerElement = (*env)->NewLocalRef(env, (*env)->FindClass(env, "sushi/hardcore/droidfs/explorers/ExplorerElement")); jclass classExplorerElement = (*env)->NewLocalRef(env, (*env)->FindClass(env, "sushi/hardcore/droidfs/explorers/ExplorerElement"));
jmethodID explorerElement_new = (*env)->GetStaticMethodID(env, classExplorerElement, "new", "(Ljava/lang/String;SJJLjava/lang/String;)Lsushi/hardcore/droidfs/explorers/ExplorerElement;"); jmethodID explorerElement_new = (*env)->GetStaticMethodID(env, classExplorerElement, "new", "(Ljava/lang/String;IJJLjava/lang/String;)Lsushi/hardcore/droidfs/explorers/ExplorerElement;");
jobject element_list = (*env)->NewObject(env, java_ArrayList, java_ArrayList_init, elements.r2); jobject element_list = (*env)->NewObject(env, java_ArrayList, java_ArrayList_init, elements.r2);
unsigned int c = 0; unsigned int c = 0;
for (unsigned int i=0; i<elements.r2; ++i){ for (unsigned int i=0; i<elements.r2; ++i){
@ -277,19 +273,15 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1list_1dir(JNIEnv *env, jobje
GoString go_name = {gcf_full_path, strlen(gcf_full_path)}; GoString go_name = {gcf_full_path, strlen(gcf_full_path)};
struct gcf_get_attrs_return attrs = gcf_get_attrs(sessionID, go_name); struct gcf_get_attrs_return attrs = gcf_get_attrs(sessionID, go_name);
short type = 0; //directory
if (S_ISREG(elements.r1[i])){
type = 1; //regular file
}
jstring jname = (*env)->NewStringUTF(env, name); jstring jname = (*env)->NewStringUTF(env, name);
jobject explorerElement = (*env)->CallStaticObjectMethod( jobject explorerElement = (*env)->CallStaticObjectMethod(
env, env,
classExplorerElement, classExplorerElement,
explorerElement_new, explorerElement_new,
jname, jname,
type, elements.r1[i],
(long long) attrs.r0, (long long) attrs.r1,
attrs.r1, attrs.r2,
jplain_dir jplain_dir
); );
(*env)->CallBooleanMethod(env, element_list, java_ArrayList_add, explorerElement); (*env)->CallBooleanMethod(env, element_list, java_ArrayList_add, explorerElement);
@ -306,8 +298,8 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1list_1dir(JNIEnv *env, jobje
return element_list; return element_list;
} }
JNIEXPORT jlong JNICALL JNIEXPORT jobject JNICALL
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1get_1size(JNIEnv *env, jobject thiz, Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1get_1attr(JNIEnv *env, jobject thiz,
jint sessionID, jstring jfile_path) { jint sessionID, jstring jfile_path) {
const char* file_path = (*env)->GetStringUTFChars(env, jfile_path, NULL); const char* file_path = (*env)->GetStringUTFChars(env, jfile_path, NULL);
GoString go_file_path = {file_path, strlen(file_path)}; GoString go_file_path = {file_path, strlen(file_path)};
@ -316,21 +308,18 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1get_1size(JNIEnv *env, jobje
(*env)->ReleaseStringUTFChars(env, jfile_path, file_path); (*env)->ReleaseStringUTFChars(env, jfile_path, file_path);
return attrs.r0; if (attrs.r3 == 1) {
} jclass stat = (*env)->FindClass(env, "sushi/hardcore/droidfs/filesystems/Stat");
jmethodID statInit = (*env)->GetMethodID(env, stat, "<init>", "(IJJ)V");
JNIEXPORT jboolean JNICALL return (*env)->NewObject(
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1path_1exists(JNIEnv *env, jobject thiz, env, stat, statInit,
jint sessionID, (jint)attrs.r0,
jstring jfile_path) { (jlong)attrs.r1,
const char* file_path = (*env)->GetStringUTFChars(env, jfile_path, NULL); (jlong)attrs.r2
GoString go_file_path = {file_path, strlen(file_path)}; );
} else {
struct gcf_get_attrs_return attrs = gcf_get_attrs(sessionID, go_file_path); return NULL;
}
(*env)->ReleaseStringUTFChars(env, jfile_path, file_path);
return attrs.r1 != 0;
} }
JNIEXPORT jint JNICALL JNIEXPORT jint JNICALL
@ -396,8 +385,15 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1read_1file(JNIEnv *env, jobj
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1truncate(JNIEnv *env, jobject thiz, Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1truncate(JNIEnv *env, jobject thiz,
jint sessionID, jint sessionID,
jint handleID, jlong offset) { jstring jpath,
return gcf_truncate(sessionID, handleID, offset); jlong offset) {
const char* path = (*env)->GetStringUTFChars(env, jpath, NULL);
GoString go_path = {path, strlen(path)};
GoUint8 result = gcf_truncate(sessionID, go_path, offset);
(*env)->ReleaseStringUTFChars(env, jpath, path);
return result;
} }
JNIEXPORT void JNICALL JNIEXPORT void JNICALL

View File

@ -0,0 +1,198 @@
#include <sys/stat.h>
#include <jni.h>
jlong cryfs_init(JNIEnv* env, jstring jbaseDir, jstring jlocalSateDir, jbyteArray jpassword);
jlong cryfs_create(JNIEnv* env, jlong fusePtr, jstring jpath, mode_t mode);
jlong cryfs_open(JNIEnv* env, jlong fusePtr, jstring jpath, jint flags);
jint cryfs_read(JNIEnv* env, jlong fusePtr, jlong fileHandle, jbyteArray jbuffer, jlong offset);
jint cryfs_write(JNIEnv* env, jlong fusePtr, jlong fileHandle, jlong offset, jbyteArray jbuffer, jint size);
jint cryfs_truncate(JNIEnv* env, jlong fusePtr, jstring jpath, jlong size);
jint cryfs_unlink(JNIEnv* env, jlong fusePtr, jstring jpath);
jint cryfs_release(jlong fusePtr, jlong fileHandle);
jlong cryfs_readdir(JNIEnv* env, jlong fusePtr, jstring jpath ,void* data, int(void*, const char*, const struct stat*));
jint cryfs_mkdir(JNIEnv* env, jlong fusePtr, jstring jpath, mode_t mode);
jint cryfs_rmdir(JNIEnv* env, jlong fusePtr, jstring jpath);
jint cryfs_getattr(JNIEnv* env, jlong fusePtr, jstring jpath, struct stat* stat);
jint cryfs_rename(JNIEnv* env, jlong fusePtr, jstring jsrcPath, jstring jdstPath);
void cryfs_destroy(jlong fusePtr);
jboolean cryfs_is_closed(jlong fusePtr);
JNIEXPORT jlong JNICALL
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeInit(JNIEnv *env, jobject thiz,
jstring base_dir, jstring jlocalStateDir,
jbyteArray password) {
return cryfs_init(env, base_dir, jlocalStateDir, password);
}
JNIEXPORT jlong JNICALL
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeCreate(JNIEnv *env, jobject thiz,
jlong fuse_ptr, jstring path,
jint mode) {
return cryfs_create(env, fuse_ptr, path, mode);
}
JNIEXPORT jlong JNICALL
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeOpen(JNIEnv *env, jobject thiz,
jlong fuse_ptr, jstring path,
jint flags) {
return cryfs_open(env, fuse_ptr, path, flags);
}
JNIEXPORT jint JNICALL
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeRead(JNIEnv *env, jobject thiz,
jlong fuse_ptr, jlong file_handle,
jbyteArray buffer, jlong offset) {
return cryfs_read(env, fuse_ptr, file_handle, buffer, offset);
}
JNIEXPORT jint JNICALL
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeWrite(JNIEnv *env,
jobject thiz,
jlong fuse_ptr,
jlong file_handle,
jlong offset,
jbyteArray buffer,
jint size) {
return cryfs_write(env, fuse_ptr, file_handle, offset, buffer, size);
}
JNIEXPORT jboolean JNICALL
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeTruncate(JNIEnv *env,
jobject thiz,
jlong fuse_ptr,
jstring path,
jlong size) {
return cryfs_truncate(env, fuse_ptr, path, size) == 0;
}
JNIEXPORT jboolean JNICALL
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeDeleteFile(JNIEnv *env,
jobject thiz,
jlong fuse_ptr,
jstring path) {
return cryfs_unlink(env, fuse_ptr, path) == 0;
}
JNIEXPORT jboolean JNICALL
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeCloseFile(JNIEnv *env,
jobject thiz,
jlong fuse_ptr,
jlong file_handle) {
return cryfs_release(fuse_ptr, file_handle) == 0;
}
struct readDirHelper {
JNIEnv* env;
jclass explorerElement;
jmethodID explorerElementNew;
jmethodID arrayListAdd;
jobject elementList;
jstring jparentPath;
};
int readDir(void* data, const char* name, const struct stat* stat) {
struct readDirHelper* helper = (struct readDirHelper*)data;
jstring jname = (*helper->env)->NewStringUTF(helper->env, name);
jobject explorerElement = (*helper->env)->CallStaticObjectMethod(
helper->env,
helper->explorerElement,
helper->explorerElementNew,
jname,
stat->st_mode,
stat->st_size,
stat->st_mtim.tv_sec,
helper->jparentPath
);
(*helper->env)->CallBooleanMethod(helper->env, helper->elementList, helper->arrayListAdd, explorerElement);
(*helper->env)->DeleteLocalRef(helper->env, explorerElement);
(*helper->env)->DeleteLocalRef(helper->env, jname);
return 0;
}
JNIEXPORT jobject JNICALL
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeReadDir(JNIEnv *env,
jobject thiz,
jlong fuse_ptr,
jstring path) {
jclass arrayList = (*env)->NewLocalRef(env, (*env)->FindClass(env, "java/util/ArrayList"));
jmethodID arrayListInit = (*env)->GetMethodID(env, arrayList, "<init>", "()V");
struct readDirHelper helper;
helper.env = env;
helper.explorerElement = (*env)->NewLocalRef(env, (*env)->FindClass(env, "sushi/hardcore/droidfs/explorers/ExplorerElement"));
helper.explorerElementNew = (*env)->GetStaticMethodID(
env, helper.explorerElement, "new",
"(Ljava/lang/String;IJJLjava/lang/String;)Lsushi/hardcore/droidfs/explorers/ExplorerElement;"
);
helper.arrayListAdd = (*env)->GetMethodID(env, arrayList, "add", "(Ljava/lang/Object;)Z");
helper.elementList = (*env)->NewObject(env, arrayList, arrayListInit);
helper.jparentPath = path;
int result = cryfs_readdir(env, fuse_ptr, path, &helper, readDir);
if (result == 0) {
return helper.elementList;
} else {
return NULL;
}
}
JNIEXPORT jboolean JNICALL
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeMkdir(JNIEnv *env,
jobject thiz,
jlong fuse_ptr,
jstring path,
jint mode) {
return cryfs_mkdir(env, fuse_ptr, path, mode) == 0;
}
JNIEXPORT jboolean JNICALL
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeRmdir(JNIEnv *env,
jobject thiz,
jlong fuse_ptr,
jstring path) {
return cryfs_rmdir(env, fuse_ptr, path) == 0;
}
JNIEXPORT jobject JNICALL
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeGetAttr(JNIEnv *env,
jobject thiz,
jlong fuse_ptr,
jstring path) {
struct stat stbuf;
int result = cryfs_getattr(env, fuse_ptr, path, &stbuf);
if (result == 0) {
jclass stat = (*env)->FindClass(env, "sushi/hardcore/droidfs/filesystems/Stat");
jmethodID statInit = (*env)->GetMethodID(env, stat, "<init>", "(IJJ)V");
return (*env)->NewObject(
env, stat, statInit,
(jint)stbuf.st_mode,
(jlong)stbuf.st_size,
(jlong)stbuf.st_mtim.tv_sec
);
} else {
return NULL;
}
}
JNIEXPORT jboolean JNICALL
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeRename(JNIEnv *env,
jobject thiz,
jlong fuse_ptr,
jstring src_path,
jstring dst_path) {
return cryfs_rename(env, fuse_ptr, src_path, dst_path) == 0;
}
JNIEXPORT void JNICALL
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeClose(JNIEnv *env,
jobject thiz,
jlong fuse_ptr) {
cryfs_destroy(fuse_ptr);
}
JNIEXPORT jboolean JNICALL
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeIsClosed(JNIEnv *env,
jobject thiz,
jlong fuse_ptr) {
return cryfs_is_closed(fuse_ptr);
}

View File

@ -39,7 +39,7 @@
<string name="external_open">فتح بتطبيق خارجي</string> <string name="external_open">فتح بتطبيق خارجي</string>
<string name="single_delete_confirm">هل أنت متأكد من حذف %s ?</string> <string name="single_delete_confirm">هل أنت متأكد من حذف %s ?</string>
<string name="multiple_delete_confirm">هل أنت متأكد من حذف هذه %s العناصر ?</string> <string name="multiple_delete_confirm">هل أنت متأكد من حذف هذه %s العناصر ?</string>
<string name="location">موقع: /%s</string> <string name="location">موقع: %s</string>
<string name="total_size">الحجم الكلي: %s</string> <string name="total_size">الحجم الكلي: %s</string>
<string name="import_from_other_volume">إستيراد من مجلد مشفر آخر</string> <string name="import_from_other_volume">إستيراد من مجلد مشفر آخر</string>
<string name="read_file_failed">لقد فشل فتح هذا الملف.</string> <string name="read_file_failed">لقد فشل فتح هذا الملف.</string>

View File

@ -39,7 +39,7 @@
<string name="external_open">Abrir con una aplicación externa</string> <string name="external_open">Abrir con una aplicación externa</string>
<string name="single_delete_confirm">¿Estás seguro de que quieres borrar %s ?</string> <string name="single_delete_confirm">¿Estás seguro de que quieres borrar %s ?</string>
<string name="multiple_delete_confirm">¿Estás seguro de que quiere borrar %s objetos?</string> <string name="multiple_delete_confirm">¿Estás seguro de que quiere borrar %s objetos?</string>
<string name="location">Ubicación: /%s</string> <string name="location">Ubicación: %s</string>
<string name="total_size">Tamaño total: %s</string> <string name="total_size">Tamaño total: %s</string>
<string name="import_from_other_volume">Importar desde otro volumen</string> <string name="import_from_other_volume">Importar desde otro volumen</string>
<string name="read_file_failed">No se ha podido abrir este archivo.</string> <string name="read_file_failed">No se ha podido abrir este archivo.</string>

View File

@ -39,7 +39,7 @@
<string name="external_open">Abrir com app externo</string> <string name="external_open">Abrir com app externo</string>
<string name="single_delete_confirm">Você tem certeza que quer excluir %s?</string> <string name="single_delete_confirm">Você tem certeza que quer excluir %s?</string>
<string name="multiple_delete_confirm">Você realmente deseja excluir estos %s itens?</string> <string name="multiple_delete_confirm">Você realmente deseja excluir estos %s itens?</string>
<string name="location">Localização: /%s</string> <string name="location">Localização: %s</string>
<string name="total_size">Tamanho total: %s</string> <string name="total_size">Tamanho total: %s</string>
<string name="import_from_other_volume">Importar do outro volume</string> <string name="import_from_other_volume">Importar do outro volume</string>
<string name="read_file_failed">Falha ao abrir este arquivo.</string> <string name="read_file_failed">Falha ao abrir este arquivo.</string>

View File

@ -37,7 +37,7 @@
<string name="external_open">Открыть внешним приложением</string> <string name="external_open">Открыть внешним приложением</string>
<string name="single_delete_confirm">Удалить %s?</string> <string name="single_delete_confirm">Удалить %s?</string>
<string name="multiple_delete_confirm">Удалить %s элементов?</string> <string name="multiple_delete_confirm">Удалить %s элементов?</string>
<string name="location">Путь: /%s</string> <string name="location">Путь: %s</string>
<string name="total_size">Общий размер: %s</string> <string name="total_size">Общий размер: %s</string>
<string name="import_from_other_volume">Импорт из другого тома</string> <string name="import_from_other_volume">Импорт из другого тома</string>
<string name="read_file_failed">Невозможно открыть файл.</string> <string name="read_file_failed">Невозможно открыть файл.</string>

View File

@ -39,7 +39,7 @@
<string name="external_open">Open with external app</string> <string name="external_open">Open with external app</string>
<string name="single_delete_confirm">Are you sure you want to delete %s ?</string> <string name="single_delete_confirm">Are you sure you want to delete %s ?</string>
<string name="multiple_delete_confirm">Are you sure you want to delete these %s items ?</string> <string name="multiple_delete_confirm">Are you sure you want to delete these %s items ?</string>
<string name="location">Location: /%s</string> <string name="location">Location: %s</string>
<string name="total_size">Total size: %s</string> <string name="total_size">Total size: %s</string>
<string name="import_from_other_volume">Import from another volume</string> <string name="import_from_other_volume">Import from another volume</string>
<string name="read_file_failed">Failed to open this file.</string> <string name="read_file_failed">Failed to open this file.</string>