forked from hardcoresushi/DroidFS
libcryfs genesis
This commit is contained in:
parent
83dd759f36
commit
dbd04848bd
@ -1,5 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
add_subdirectory(${PROJECT_SOURCE_DIR}/libcryfs/)
|
||||
|
||||
add_library(
|
||||
gocryptfs
|
||||
SHARED
|
||||
@ -23,6 +25,10 @@ target_link_libraries(
|
||||
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(
|
||||
avformat
|
||||
SHARED
|
||||
|
@ -4,7 +4,7 @@ apply plugin: 'kotlin-android'
|
||||
android {
|
||||
compileSdkVersion 31
|
||||
buildToolsVersion "31"
|
||||
ndkVersion "23.1.7779620"
|
||||
ndkVersion "24.0.8215888"
|
||||
|
||||
compileOptions {
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
|
@ -30,6 +30,7 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
|
||||
import sushi.hardcore.droidfs.databinding.ActivityCameraBinding
|
||||
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
import sushi.hardcore.droidfs.video_recording.SeekableWriter
|
||||
import sushi.hardcore.droidfs.video_recording.VideoCapture
|
||||
@ -64,7 +65,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
||||
private lateinit var sensorOrientationListener: SensorOrientationListener
|
||||
private var previousOrientation: Float = 0f
|
||||
private lateinit var orientedIcons: List<ImageView>
|
||||
private lateinit var gocryptfsVolume: GocryptfsVolume
|
||||
private lateinit var encryptedVolume: EncryptedVolume
|
||||
private lateinit var outputDirectory: String
|
||||
private var isFinishingIntentionally = false
|
||||
private var isAskingPermissions = false
|
||||
@ -93,7 +94,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
||||
binding = ActivityCameraBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
supportActionBar?.hide()
|
||||
gocryptfsVolume = GocryptfsVolume(applicationContext, intent.getIntExtra("sessionID", -1))
|
||||
encryptedVolume = intent.getParcelableExtra("volume")!!
|
||||
outputDirectory = intent.getStringExtra("path")!!
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
@ -381,7 +382,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
||||
var fileName: String
|
||||
do {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -415,7 +416,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
||||
imageCapture.takePicture(outputOptions, executor, object : ImageCapture.OnImageSavedCallback {
|
||||
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
||||
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()
|
||||
} else {
|
||||
CustomAlertDialogBuilder(this@CameraActivity, themeValue)
|
||||
@ -446,18 +447,18 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
||||
isRecording = false
|
||||
} else if (!isWaitingForTimer) {
|
||||
val path = getOutputPath(true)
|
||||
startTimerThen {
|
||||
val handleId = gocryptfsVolume.openWriteMode(path)
|
||||
/*startTimerThen {
|
||||
val handleId = encryptedVolume.openWriteMode(path)
|
||||
videoCapture?.startRecording(VideoCapture.OutputFileOptions(object : SeekableWriter {
|
||||
var offset = 0L
|
||||
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) {
|
||||
this.offset = offset
|
||||
}
|
||||
override fun close() {
|
||||
gocryptfsVolume.closeFile(handleId)
|
||||
encryptedVolume.closeFile(handleId)
|
||||
}
|
||||
}), executor, object : VideoCapture.OnVideoSavedCallback {
|
||||
override fun onVideoSaved() {
|
||||
@ -472,14 +473,14 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
||||
})
|
||||
binding.recordVideoButton.setImageResource(R.drawable.stop_recording_video_button)
|
||||
isRecording = true
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (!isFinishingIntentionally) {
|
||||
gocryptfsVolume.close()
|
||||
encryptedVolume.close()
|
||||
RestrictedFileProvider.wipeAll(this)
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import java.util.*
|
||||
class ChangePasswordActivity: BaseActivity() {
|
||||
|
||||
private lateinit var binding: ActivityChangePasswordBinding
|
||||
private lateinit var volume: Volume
|
||||
private lateinit var volume: SavedVolume
|
||||
private lateinit var volumeDatabase: VolumeDatabase
|
||||
private var fingerprintProtector: FingerprintProtector? = null
|
||||
private var usfFingerprint: Boolean = false
|
||||
|
@ -5,8 +5,6 @@ import java.io.File
|
||||
|
||||
object ConstValues {
|
||||
const val CREATOR = "DroidFS"
|
||||
const val FILE_MODE = 384 //0600
|
||||
const val DIRECTORY_MODE = 448 //0700
|
||||
const val VOLUME_DATABASE_NAME = "SavedVolumes"
|
||||
const val SORT_ORDER_KEY = "sort_order"
|
||||
val FAKE_URI: Uri = Uri.parse("fakeuri://droidfs")
|
||||
|
@ -133,7 +133,7 @@ class FingerprintProtector private constructor(
|
||||
private lateinit var cipher: Cipher
|
||||
private var isCipherReady = false
|
||||
private var cipherActionMode: Int? = null
|
||||
private lateinit var volume: Volume
|
||||
private lateinit var volume: SavedVolume
|
||||
private lateinit var dataToProcess: ByteArray
|
||||
|
||||
private fun resetHashStorage() {
|
||||
@ -207,7 +207,7 @@ class FingerprintProtector private constructor(
|
||||
.show()
|
||||
}
|
||||
|
||||
fun savePasswordHash(volume: Volume, plainText: ByteArray) {
|
||||
fun savePasswordHash(volume: SavedVolume, plainText: ByteArray) {
|
||||
this.volume = volume
|
||||
val biometricPromptInfo = BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(activity.getString(R.string.encrypt_action_description))
|
||||
|
@ -1,15 +1,11 @@
|
||||
package sushi.hardcore.droidfs
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Parcel
|
||||
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
|
||||
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||
import sushi.hardcore.droidfs.filesystems.Stat
|
||||
|
||||
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_is_closed(sessionID: Int): Boolean
|
||||
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_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_truncate(sessionID: Int, handleID: Int, 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_truncate(sessionID: Int, path: String, offset: Long): Boolean
|
||||
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_mkdir(sessionID: Int, dir_path: String, mode: Int): 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
|
||||
|
||||
companion object {
|
||||
@ -32,14 +27,16 @@ class GocryptfsVolume(val applicationContext: Context, var sessionID: Int) {
|
||||
const val DefaultBS = 4096
|
||||
const val CONFIG_FILE_NAME = "gocryptfs.conf"
|
||||
external fun createVolume(root_cipher_dir: String, password: CharArray, plainTextNames: Boolean, xchacha: Int, logN: Int, creator: String, returnedHash: ByteArray?): Boolean
|
||||
external fun 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
|
||||
|
||||
fun isGocryptfsVolume(path: File): Boolean {
|
||||
if (path.isDirectory){
|
||||
return File(path, CONFIG_FILE_NAME).isFile
|
||||
fun init(root_cipher_dir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ByteArray?): GocryptfsVolume? {
|
||||
val sessionId = nativeInit(root_cipher_dir, password, givenHash, returnedHash)
|
||||
return if (sessionId == -1) {
|
||||
null
|
||||
} else {
|
||||
GocryptfsVolume(sessionId)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fun isClosed(): Boolean {
|
||||
override fun isClosed(): Boolean {
|
||||
return native_is_closed(sessionID)
|
||||
}
|
||||
|
||||
fun listDir(dir_path: String): MutableList<ExplorerElement> {
|
||||
return native_list_dir(sessionID, dir_path)
|
||||
override fun mkdir(path: String): Boolean {
|
||||
return native_mkdir(sessionID, path, 0)
|
||||
}
|
||||
|
||||
fun mkdir(dir_path: String): Boolean {
|
||||
return native_mkdir(sessionID, dir_path, ConstValues.DIRECTORY_MODE)
|
||||
override fun rmdir(path: String): Boolean {
|
||||
return native_rmdir(sessionID, path)
|
||||
}
|
||||
|
||||
fun rmdir(dir_path: String): Boolean {
|
||||
return native_rmdir(sessionID, dir_path)
|
||||
}
|
||||
|
||||
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()
|
||||
override fun closeFile(fileHandle: Long): Boolean {
|
||||
native_close_file(sessionID, fileHandle.toInt())
|
||||
return true
|
||||
}
|
||||
|
||||
fun exportFile(src_path: String, os: OutputStream): Boolean {
|
||||
var success = false
|
||||
val srcHandleId = openReadMode(src_path)
|
||||
if (srcHandleId != -1) {
|
||||
success = exportFile(srcHandleId, os)
|
||||
closeFile(srcHandleId)
|
||||
}
|
||||
return success
|
||||
override fun write(fileHandle: Long, offset: Long, buffer: ByteArray, size: Int): Int {
|
||||
return native_write_file(sessionID, fileHandle.toInt(), offset, buffer, size)
|
||||
}
|
||||
|
||||
fun exportFile(src_path: String, dst_path: String): Boolean {
|
||||
return exportFile(src_path, FileOutputStream(dst_path))
|
||||
override fun truncate(path: String, size: Long): Boolean {
|
||||
return native_truncate(sessionID, path, size)
|
||||
}
|
||||
|
||||
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
|
||||
override fun deleteFile(path: String): Boolean {
|
||||
return native_remove_file(sessionID, path)
|
||||
}
|
||||
|
||||
fun importFile(inputStream: InputStream, dst_path: String): Boolean {
|
||||
val dstHandleId = openWriteMode(dst_path)
|
||||
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)
|
||||
}
|
||||
override fun rename(srcPath: String, dstPath: String): Boolean {
|
||||
return native_rename(sessionID, srcPath, dstPath)
|
||||
}
|
||||
}
|
@ -30,10 +30,13 @@ import sushi.hardcore.droidfs.explorers.ExplorerActivity
|
||||
import sushi.hardcore.droidfs.explorers.ExplorerActivityDrop
|
||||
import sushi.hardcore.droidfs.explorers.ExplorerActivityPick
|
||||
import sushi.hardcore.droidfs.file_operations.FileOperationService
|
||||
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||
import sushi.hardcore.droidfs.widgets.EditTextDialog
|
||||
import java.io.File
|
||||
import java.nio.CharBuffer
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.*
|
||||
|
||||
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())
|
||||
openVolume(volume, position)
|
||||
else
|
||||
@ -186,7 +189,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
||||
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 (volumes[i].isHidden) {
|
||||
if (doDeleteVolumeContent == null) {
|
||||
@ -306,7 +309,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
||||
DocumentFile.fromFile(File(volume.name)),
|
||||
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 ->
|
||||
val path = PathUtils.getFullPathFromTreeUri(dstRootDirectory.uri, this)
|
||||
if (path == null) null
|
||||
else Volume(
|
||||
else SavedVolume(
|
||||
PathUtils.pathJoin(path, name),
|
||||
false,
|
||||
volume.type,
|
||||
volume.encryptedHash,
|
||||
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 {
|
||||
val result = fileOperationService.copyVolume(srcDocumentFile, dstDocumentFile)
|
||||
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 ->
|
||||
val srcPath = File(volume.getFullPath(filesDir.path))
|
||||
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
|
||||
private fun openVolume(volume: Volume, position: Int) {
|
||||
private fun openVolume(volume: SavedVolume, position: Int) {
|
||||
var askForPassword = true
|
||||
fingerprintProtector?.let { fingerprintProtector ->
|
||||
volume.encryptedHash?.let { encryptedHash ->
|
||||
@ -463,21 +467,21 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
||||
volumeAdapter.refresh()
|
||||
}
|
||||
override fun onPasswordHashDecrypted(hash: ByteArray) {
|
||||
object : LoadingTask<Int>(this@MainActivity, themeValue, R.string.loading_msg_open) {
|
||||
override suspend fun doTask(): Int {
|
||||
val sessionId = GocryptfsVolume.init(volume.getFullPath(filesDir.path), null, hash, null)
|
||||
object : LoadingTask<EncryptedVolume?>(this@MainActivity, themeValue, R.string.loading_msg_open) {
|
||||
override suspend fun doTask(): EncryptedVolume? {
|
||||
val encryptedVolume = EncryptedVolume.init(volume, filesDir.path, null, hash, null)
|
||||
Arrays.fill(hash, 0)
|
||||
return sessionId
|
||||
return encryptedVolume
|
||||
}
|
||||
}.startTask(lifecycleScope) { sessionId ->
|
||||
if (sessionId != -1) {
|
||||
startExplorer(sessionId, volume.shortName)
|
||||
} else {
|
||||
}.startTask(lifecycleScope) { encryptedVolume ->
|
||||
if (encryptedVolume == null) {
|
||||
CustomAlertDialogBuilder(this@MainActivity, themeValue)
|
||||
.setTitle(R.string.open_volume_failed)
|
||||
.setMessage(R.string.open_failed_hash_msg)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
} else {
|
||||
startExplorer(encryptedVolume, volume.shortName)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -496,7 +500,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
||||
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)) {
|
||||
with (sharedPrefs.edit()) {
|
||||
defaultVolumeName = if (dialogBinding.checkboxDefaultOpen.isChecked) {
|
||||
@ -515,12 +519,12 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
||||
openVolumeWithPassword(
|
||||
volume,
|
||||
position,
|
||||
password,
|
||||
StandardCharsets.UTF_8.encode(CharBuffer.wrap(password)).array(),
|
||||
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)
|
||||
if (!usfFingerprint || fingerprintProtector == null || volume.encryptedHash != null) {
|
||||
dialogBinding.checkboxSavePassword.visibility = View.GONE
|
||||
@ -550,20 +554,28 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
||||
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)
|
||||
var returnedHash: ByteArray? = null
|
||||
if (savePasswordHash && usfFingerprint) {
|
||||
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
|
||||
}
|
||||
object : LoadingTask<Int>(this, themeValue, R.string.loading_msg_open) {
|
||||
override suspend fun doTask(): Int {
|
||||
val sessionId = GocryptfsVolume.init(volume.getFullPath(filesDir.path), password, null, returnedHash)
|
||||
Arrays.fill(password, 0.toChar())
|
||||
return sessionId
|
||||
object : LoadingTask<EncryptedVolume?>(this, themeValue, R.string.loading_msg_open) {
|
||||
override suspend fun doTask(): EncryptedVolume? {
|
||||
val encryptedVolume = EncryptedVolume.init(volume, filesDir.path, password, null, returnedHash)
|
||||
Arrays.fill(password, 0)
|
||||
return encryptedVolume
|
||||
}
|
||||
}.startTask(lifecycleScope) { sessionId ->
|
||||
if (sessionId != -1) {
|
||||
}.startTask(lifecycleScope) { encryptedVolume ->
|
||||
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
|
||||
@SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23
|
||||
if (savePasswordHash && returnedHash != null && fingerprintProtector != null) {
|
||||
@ -575,12 +587,12 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
||||
override fun onPasswordHashSaved() {
|
||||
Arrays.fill(returnedHash, 0)
|
||||
volumeAdapter.onVolumeChanged(position)
|
||||
startExplorer(sessionId, volume.shortName)
|
||||
startExplorer(encryptedVolume, volume.shortName)
|
||||
}
|
||||
private var isClosed = false
|
||||
override fun onFailed(pending: Boolean) {
|
||||
if (!isClosed) {
|
||||
GocryptfsVolume(this@MainActivity, sessionId).close()
|
||||
encryptedVolume.close()
|
||||
isClosed = true
|
||||
}
|
||||
Arrays.fill(returnedHash, 0)
|
||||
@ -588,21 +600,13 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
||||
}
|
||||
fingerprintProtector.savePasswordHash(volume, returnedHash)
|
||||
} 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
|
||||
if (dropMode) { //import via android share menu
|
||||
explorerIntent = Intent(this, ExplorerActivityDrop::class.java)
|
||||
@ -610,13 +614,13 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
||||
explorerIntent.putExtras(intent.extras!!) //forward extras
|
||||
} else if (pickMode) {
|
||||
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
|
||||
}
|
||||
if (explorerIntent == null) {
|
||||
explorerIntent = Intent(this, ExplorerActivity::class.java) //default opening
|
||||
}
|
||||
explorerIntent.putExtra("sessionID", sessionId)
|
||||
explorerIntent.putExtra("volume", encryptedVolume)
|
||||
explorerIntent.putExtra("volume_name", volumeShortName)
|
||||
startActivity(explorerIntent)
|
||||
if (pickMode)
|
||||
@ -642,7 +646,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
||||
if (shouldCloseVolume) {
|
||||
val sessionID = intent.getIntExtra("sessionID", -1)
|
||||
if (sessionID != -1) {
|
||||
GocryptfsVolume(this, sessionID).close()
|
||||
GocryptfsVolume(sessionID).close()
|
||||
RestrictedFileProvider.wipeAll(this)
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,12 @@ import android.os.Parcelable
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
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(
|
||||
parcel.readString()!!,
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readByte(),
|
||||
parcel.createByteArray(),
|
||||
parcel.createByteArray()
|
||||
)
|
||||
@ -20,7 +21,7 @@ class Volume(val name: String, val isHidden: Boolean = false, var encryptedHash:
|
||||
|
||||
fun getFullPath(filesDir: String): String {
|
||||
return if (isHidden)
|
||||
PathUtils.pathJoin(filesDir, name)
|
||||
getHiddenVolumeFullPath(filesDir, name)
|
||||
else
|
||||
name
|
||||
}
|
||||
@ -37,18 +38,23 @@ class Volume(val name: String, val isHidden: Boolean = false, var encryptedHash:
|
||||
with (dest) {
|
||||
writeString(name)
|
||||
writeByte(if (isHidden) 1 else 0)
|
||||
writeByte(type)
|
||||
writeByteArray(encryptedHash)
|
||||
writeByteArray(iv)
|
||||
}
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<Volume> {
|
||||
override fun createFromParcel(parcel: Parcel): Volume {
|
||||
return Volume(parcel)
|
||||
companion object {
|
||||
const val VOLUMES_DIRECTORY = "volumes"
|
||||
|
||||
@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?> {
|
||||
return arrayOfNulls(size)
|
||||
fun getHiddenVolumeFullPath(filesDir: String, name: String): String {
|
||||
return PathUtils.pathJoin(filesDir, VOLUMES_DIRECTORY, name)
|
||||
}
|
||||
}
|
||||
}
|
@ -4,20 +4,25 @@ import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
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,
|
||||
ConstValues.VOLUME_DATABASE_NAME, null, 3) {
|
||||
class VolumeDatabase(private val context: Context): SQLiteOpenHelper(context, ConstValues.VOLUME_DATABASE_NAME, null, 4) {
|
||||
companion object {
|
||||
const val TABLE_NAME = "Volumes"
|
||||
const val COLUMN_NAME = "name"
|
||||
const val COLUMN_HIDDEN = "hidden"
|
||||
const val COLUMN_TYPE = "type"
|
||||
const val COLUMN_HASH = "hash"
|
||||
const val COLUMN_IV = "iv"
|
||||
|
||||
private fun contentValuesFromVolume(volume: Volume): ContentValues {
|
||||
private fun contentValuesFromVolume(volume: SavedVolume): ContentValues {
|
||||
val contentValues = ContentValues()
|
||||
contentValues.put(COLUMN_NAME, volume.name)
|
||||
contentValues.put(COLUMN_HIDDEN, volume.isHidden)
|
||||
contentValues.put(COLUMN_TYPE, byteArrayOf(volume.type))
|
||||
contentValues.put(COLUMN_HASH, volume.encryptedHash)
|
||||
contentValues.put(COLUMN_IV, volume.iv)
|
||||
return contentValues
|
||||
@ -25,11 +30,57 @@ class VolumeDatabase(context: Context): SQLiteOpenHelper(context,
|
||||
}
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
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 {
|
||||
val cursor = readableDatabase.query(TABLE_NAME,
|
||||
@ -42,23 +93,30 @@ class VolumeDatabase(context: Context): SQLiteOpenHelper(context,
|
||||
return result
|
||||
}
|
||||
|
||||
fun saveVolume(volume: Volume): Boolean {
|
||||
fun saveVolume(volume: SavedVolume): Boolean {
|
||||
if (!isVolumeSaved(volume.name, volume.isHidden)) {
|
||||
return (writableDatabase.insert(TABLE_NAME, null, contentValuesFromVolume(volume)) == 0.toLong())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun getVolumes(): List<Volume> {
|
||||
val list: MutableList<Volume> = ArrayList()
|
||||
fun getVolumes(): List<SavedVolume> {
|
||||
val list: MutableList<SavedVolume> = ArrayList()
|
||||
val cursor = readableDatabase.rawQuery("SELECT * FROM $TABLE_NAME", null)
|
||||
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(
|
||||
Volume(
|
||||
cursor.getString(cursor.getColumnIndex(COLUMN_NAME)),
|
||||
cursor.getShort(cursor.getColumnIndex(COLUMN_HIDDEN)) == 1.toShort(),
|
||||
cursor.getBlob(cursor.getColumnIndex(COLUMN_HASH)),
|
||||
cursor.getBlob(cursor.getColumnIndex(COLUMN_IV))
|
||||
SavedVolume(
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NAME)),
|
||||
cursor.getShort(cursor.getColumnIndexOrThrow(COLUMN_HIDDEN)) == 1.toShort(),
|
||||
volumeType,
|
||||
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)
|
||||
var isHashSaved = false
|
||||
if (cursor.moveToNext()) {
|
||||
if (cursor.getBlob(cursor.getColumnIndex(COLUMN_HASH)) != null) {
|
||||
if (cursor.getBlob(cursor.getColumnIndexOrThrow(COLUMN_HASH)) != null) {
|
||||
isHashSaved = true
|
||||
}
|
||||
}
|
||||
@ -78,16 +136,17 @@ class VolumeDatabase(context: Context): SQLiteOpenHelper(context,
|
||||
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
|
||||
}
|
||||
|
||||
fun removeHash(volume: Volume): Boolean {
|
||||
fun removeHash(volume: SavedVolume): Boolean {
|
||||
return writableDatabase.update(
|
||||
TABLE_NAME, contentValuesFromVolume(
|
||||
Volume(
|
||||
SavedVolume(
|
||||
volume.name,
|
||||
volume.isHidden,
|
||||
volume.type,
|
||||
null,
|
||||
null
|
||||
)
|
||||
|
@ -17,16 +17,17 @@ import com.bumptech.glide.request.target.DrawableImageViewTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import kotlinx.coroutines.*
|
||||
import sushi.hardcore.droidfs.ConstValues
|
||||
import sushi.hardcore.droidfs.GocryptfsVolume
|
||||
import sushi.hardcore.droidfs.R
|
||||
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 java.text.DateFormat
|
||||
import java.util.*
|
||||
|
||||
class ExplorerElementAdapter(
|
||||
val activity: AppCompatActivity,
|
||||
val gocryptfsVolume: GocryptfsVolume?,
|
||||
val encryptedVolume: EncryptedVolume?,
|
||||
private val listener: Listener,
|
||||
val thumbnailMaxSize: Long,
|
||||
) : SelectableAdapter<ExplorerElement>(listener::onSelectionChanged) {
|
||||
@ -42,7 +43,7 @@ class ExplorerElementAdapter(
|
||||
private var thumbnailsCache: LruCache<String, Bitmap>? = null
|
||||
|
||||
init {
|
||||
if (gocryptfsVolume != null) {
|
||||
if (encryptedVolume != null) {
|
||||
thumbnailsCache = LruCache((Runtime.getRuntime().maxMemory() / 1024 / 8).toInt())
|
||||
}
|
||||
}
|
||||
@ -105,9 +106,9 @@ class ExplorerElementAdapter(
|
||||
open class RegularElementViewHolder(itemView: View) : ExplorerElementViewHolder(itemView) {
|
||||
open fun bind(explorerElement: ExplorerElement, position: Int, isSelected: Boolean) {
|
||||
super.bind(explorerElement, position)
|
||||
textElementSize.text = PathUtils.formatSize(explorerElement.size)
|
||||
textElementSize.text = PathUtils.formatSize(explorerElement.stat.size)
|
||||
(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 fun loadThumbnail(fullPath: String, adapter: ExplorerElementAdapter) {
|
||||
adapter.gocryptfsVolume?.let { volume ->
|
||||
adapter.encryptedVolume?.let { volume ->
|
||||
job = scope.launch {
|
||||
volume.loadWholeFile(fullPath, maxSize = adapter.thumbnailMaxSize).first?.let {
|
||||
if (isActive) {
|
||||
@ -220,9 +221,9 @@ class ExplorerElementAdapter(
|
||||
}, parent, false
|
||||
)
|
||||
return when (viewType) {
|
||||
ExplorerElement.REGULAR_FILE_TYPE -> FileViewHolder(view)
|
||||
ExplorerElement.DIRECTORY_TYPE -> DirectoryViewHolder(view)
|
||||
ExplorerElement.PARENT_FOLDER_TYPE -> ParentFolderViewHolder(view)
|
||||
Stat.S_IFREG -> FileViewHolder(view)
|
||||
Stat.S_IFDIR -> DirectoryViewHolder(view)
|
||||
Stat.PARENT_FOLDER_TYPE -> ParentFolderViewHolder(view)
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
@ -237,6 +238,6 @@ class ExplorerElementAdapter(
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return explorerElements[position].elementType.toInt()
|
||||
return explorerElements[position].stat.type
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import sushi.hardcore.droidfs.R
|
||||
import sushi.hardcore.droidfs.Volume
|
||||
import sushi.hardcore.droidfs.SavedVolume
|
||||
import sushi.hardcore.droidfs.VolumeDatabase
|
||||
|
||||
class VolumeAdapter(
|
||||
@ -19,9 +19,9 @@ class VolumeAdapter(
|
||||
private val allowSelection: Boolean,
|
||||
private val showReadOnly: Boolean,
|
||||
private val listener: Listener,
|
||||
) : SelectableAdapter<Volume>(listener::onSelectionChanged) {
|
||||
) : SelectableAdapter<SavedVolume>(listener::onSelectionChanged) {
|
||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||
lateinit var volumes: List<Volume>
|
||||
lateinit var volumes: List<SavedVolume>
|
||||
|
||||
init {
|
||||
reloadVolumes()
|
||||
@ -29,11 +29,11 @@ class VolumeAdapter(
|
||||
|
||||
interface Listener {
|
||||
fun onSelectionChanged(size: Int)
|
||||
fun onVolumeItemClick(volume: Volume, position: Int)
|
||||
fun onVolumeItemClick(volume: SavedVolume, position: Int)
|
||||
fun onVolumeItemLongClick()
|
||||
}
|
||||
|
||||
override fun getItems(): List<Volume> {
|
||||
override fun getItems(): List<SavedVolume> {
|
||||
return volumes
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import sushi.hardcore.droidfs.*
|
||||
import sushi.hardcore.droidfs.databinding.FragmentCreateVolumeBinding
|
||||
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
@ -130,8 +131,8 @@ class CreateVolumeFragment: Fragment() {
|
||||
var returnedHash: ByteArray? = null
|
||||
if (binding.checkboxSavePassword.isChecked)
|
||||
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
|
||||
object: LoadingTask<Volume?>(requireActivity() as AppCompatActivity, themeValue, R.string.loading_msg_create) {
|
||||
override suspend fun doTask(): Volume? {
|
||||
object: LoadingTask<SavedVolume?>(requireActivity() as AppCompatActivity, themeValue, R.string.loading_msg_create) {
|
||||
override suspend fun doTask(): SavedVolume? {
|
||||
val xchacha = when (binding.spinnerXchacha.selectedItemPosition) {
|
||||
0 -> 0
|
||||
1 -> 1
|
||||
@ -151,7 +152,7 @@ class CreateVolumeFragment: Fragment() {
|
||||
)
|
||||
) {
|
||||
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 {
|
||||
if (isVolumeSaved(volumeName, isHiddenVolume)) // cleaning old saved path
|
||||
removeVolume(volumeName)
|
||||
|
@ -11,7 +11,6 @@ import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
@ -19,6 +18,7 @@ import androidx.fragment.app.Fragment
|
||||
import sushi.hardcore.droidfs.*
|
||||
import sushi.hardcore.droidfs.databinding.DialogSdcardErrorBinding
|
||||
import sushi.hardcore.droidfs.databinding.FragmentSelectPathBinding
|
||||
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||
import java.io.File
|
||||
@ -158,7 +158,7 @@ class SelectPathFragment: Fragment() {
|
||||
|
||||
private fun getCurrentVolumePath(): String {
|
||||
return if (binding.switchHiddenVolume.isChecked)
|
||||
PathUtils.pathJoin(requireContext().filesDir.path, binding.editVolumeName.text.toString())
|
||||
SavedVolume.getHiddenVolumeFullPath(requireContext().filesDir.path, binding.editVolumeName.text.toString())
|
||||
else
|
||||
binding.editVolumeName.text.toString()
|
||||
}
|
||||
@ -222,7 +222,8 @@ class SelectPathFragment: Fragment() {
|
||||
(activity as AddVolumeActivity).createVolume(volumePath, isHidden)
|
||||
}
|
||||
Action.ADD -> {
|
||||
if (!GocryptfsVolume.isGocryptfsVolume(File(volumePath))) {
|
||||
val volumeType = EncryptedVolume.getVolumeType(volumePath)
|
||||
if (volumeType < 0) {
|
||||
CustomAlertDialogBuilder(requireContext(), themeValue)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(R.string.error_not_a_volume)
|
||||
@ -232,7 +233,7 @@ class SelectPathFragment: Fragment() {
|
||||
val dialog = CustomAlertDialogBuilder(requireContext(), themeValue)
|
||||
.setTitle(R.string.warning)
|
||||
.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()))
|
||||
dialog.setView(
|
||||
DialogSdcardErrorBinding.inflate(layoutInflater).apply {
|
||||
@ -244,7 +245,7 @@ class SelectPathFragment: Fragment() {
|
||||
dialog.setMessage(R.string.add_cant_write_warning)
|
||||
dialog.show()
|
||||
} 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()
|
||||
}
|
||||
|
||||
private fun addVolume(volumeName: String, isHidden: Boolean) {
|
||||
volumeDatabase.saveVolume(Volume(volumeName, isHidden))
|
||||
private fun addVolume(volumeName: String, isHidden: Boolean, volumeType: Byte) {
|
||||
volumeDatabase.saveVolume(SavedVolume(volumeName, isHidden, volumeType))
|
||||
(activity as AddVolumeActivity).onVolumeAdded(false)
|
||||
}
|
||||
}
|
@ -9,9 +9,9 @@ import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import sushi.hardcore.droidfs.GocryptfsVolume
|
||||
import sushi.hardcore.droidfs.LoadingTask
|
||||
import sushi.hardcore.droidfs.R
|
||||
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||
import java.io.File
|
||||
|
||||
@ -33,25 +33,25 @@ object ExternalProvider {
|
||||
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 tmpFileUri = RestrictedFileProvider.newFile(fileName)
|
||||
if (tmpFileUri != null){
|
||||
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(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
|
||||
val uris = ArrayList<Uri>(file_paths.size)
|
||||
object : LoadingTask<String?>(activity, themeValue, R.string.loading_msg_export) {
|
||||
override suspend fun doTask(): String? {
|
||||
for (path in file_paths) {
|
||||
val result = exportFile(activity, gocryptfsVolume, path, contentType)
|
||||
val result = exportFile(activity, encryptedVolume, path, contentType)
|
||||
contentType = if (result.first != null) {
|
||||
uris.add(result.first!!)
|
||||
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) {
|
||||
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) {
|
||||
Intent(Intent.ACTION_VIEW).apply {
|
||||
setDataAndType(result.first, result.second)
|
||||
|
@ -7,6 +7,7 @@ import android.content.ServiceConnection
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
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.OperationFile
|
||||
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.widgets.CustomAlertDialogBuilder
|
||||
import sushi.hardcore.droidfs.widgets.EditTextDialog
|
||||
@ -50,7 +53,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
||||
private var foldersFirst = true
|
||||
private var mapFolders = true
|
||||
private var currentSortOrderIndex = 0
|
||||
protected lateinit var gocryptfsVolume: GocryptfsVolume
|
||||
protected lateinit var encryptedVolume: EncryptedVolume
|
||||
private lateinit var volumeName: String
|
||||
private lateinit var explorerViewModel: ExplorerViewModel
|
||||
protected var currentDirectoryPath: String = ""
|
||||
@ -82,8 +85,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
||||
usf_open = sharedPrefs.getBoolean("usf_open", false)
|
||||
usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false)
|
||||
volumeName = intent.getStringExtra("volume_name") ?: ""
|
||||
val sessionID = intent.getIntExtra("sessionID", -1)
|
||||
gocryptfsVolume = GocryptfsVolume(applicationContext, sessionID)
|
||||
encryptedVolume = intent.getParcelableExtra("volume")!!
|
||||
sortOrderEntries = resources.getStringArray(R.array.sort_orders_entries)
|
||||
sortOrderValues = resources.getStringArray(R.array.sort_orders_values)
|
||||
foldersFirst = sharedPrefs.getBoolean("folders_first", true)
|
||||
@ -107,7 +109,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
||||
explorerAdapter = ExplorerElementAdapter(
|
||||
this,
|
||||
if (sharedPrefs.getBoolean("thumbnails", true)) {
|
||||
gocryptfsVolume
|
||||
encryptedVolume
|
||||
} else {
|
||||
null
|
||||
},
|
||||
@ -139,7 +141,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
||||
}
|
||||
|
||||
class ExplorerViewModel: ViewModel() {
|
||||
var currentDirectoryPath = ""
|
||||
var currentDirectoryPath = "/"
|
||||
}
|
||||
|
||||
private fun setRecyclerViewLayout() {
|
||||
@ -166,7 +168,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
||||
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
||||
val binder = service as FileOperationService.LocalBinder
|
||||
fileOperationService = binder.getService()
|
||||
binder.setGocryptfsVolume(gocryptfsVolume)
|
||||
binder.setEncryptedVolume(encryptedVolume)
|
||||
}
|
||||
override fun onServiceDisconnected(arg0: ComponentName) {
|
||||
|
||||
@ -178,7 +180,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
||||
private fun startFileViewer(cls: Class<*>, filePath: String){
|
||||
val intent = Intent(this, cls).apply {
|
||||
putExtra("path", filePath)
|
||||
putExtra("sessionID", gocryptfsVolume.sessionID)
|
||||
putExtra("volume", encryptedVolume)
|
||||
putExtra("sortOrder", sortOrderValues[currentSortOrderIndex])
|
||||
}
|
||||
isStartingActivity = true
|
||||
@ -187,7 +189,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
||||
|
||||
private fun openWithExternalApp(fullPath: String){
|
||||
isStartingActivity = true
|
||||
ExternalProvider.open(this, themeValue, gocryptfsVolume, fullPath)
|
||||
ExternalProvider.open(this, themeValue, encryptedVolume, fullPath)
|
||||
}
|
||||
|
||||
private fun showOpenAsDialog(path: String) {
|
||||
@ -276,11 +278,11 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
||||
}
|
||||
|
||||
private fun recursiveSetSize(directory: ExplorerElement) {
|
||||
for (child in gocryptfsVolume.listDir(directory.fullPath)) {
|
||||
for (child in encryptedVolume.readDir(directory.fullPath) ?: return) {
|
||||
if (child.isDirectory) {
|
||||
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) {
|
||||
synchronized(this) {
|
||||
explorerElements = gocryptfsVolume.listDir(path)
|
||||
if (path.isNotEmpty()) { //not root
|
||||
explorerElements = encryptedVolume.readDir(path) ?: return
|
||||
if (path != "/") {
|
||||
explorerElements.add(
|
||||
0,
|
||||
ExplorerElement("..", (-1).toShort(), parentPath = currentDirectoryPath)
|
||||
ExplorerElement("..", Stat.parentFolderStat(), parentPath = currentDirectoryPath)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -323,7 +325,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
||||
if (element.isDirectory) {
|
||||
recursiveSetSize(element)
|
||||
}
|
||||
totalSize += element.size
|
||||
totalSize += element.stat.size
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -331,7 +333,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
||||
onDisplayed?.invoke()
|
||||
}
|
||||
} else {
|
||||
displayExplorerElements(explorerElements.filter { !it.isParentFolder }.sumOf { it.size })
|
||||
displayExplorerElements(explorerElements.filter { !it.isParentFolder }.sumOf { it.stat.size })
|
||||
onDisplayed?.invoke()
|
||||
}
|
||||
}
|
||||
@ -362,7 +364,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
||||
if (folderName.isEmpty()) {
|
||||
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
if (!gocryptfsVolume.mkdir(PathUtils.pathJoin(currentDirectoryPath, folderName))) {
|
||||
if (!encryptedVolume.mkdir(PathUtils.pathJoin(currentDirectoryPath, folderName))) {
|
||||
CustomAlertDialogBuilder(this, themeValue)
|
||||
.setTitle(R.string.error)
|
||||
.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) {
|
||||
val srcDirectoryPath = items[0].explorerElement.parentPath
|
||||
val srcDirectoryPath = items[0].parentPath
|
||||
var ready = true
|
||||
for (i in 0 until items.size) {
|
||||
val testDstPath: String
|
||||
if (items[i].dstPath == null){
|
||||
testDstPath = PathUtils.pathJoin(dstDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, items[i].explorerElement.fullPath))
|
||||
if (gocryptfsVolume.pathExists(testDstPath)){
|
||||
testDstPath = PathUtils.pathJoin(dstDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, items[i].srcPath))
|
||||
if (encryptedVolume.pathExists(testDstPath)) {
|
||||
ready = false
|
||||
} else {
|
||||
items[i].dstPath = testDstPath
|
||||
}
|
||||
} else {
|
||||
testDstPath = items[i].dstPath!!
|
||||
if (gocryptfsVolume.pathExists(testDstPath) && !items[i].overwriteConfirmed){
|
||||
if (encryptedVolume.pathExists(testDstPath) && !items[i].overwriteConfirmed) {
|
||||
ready = false
|
||||
}
|
||||
}
|
||||
if (!ready){
|
||||
CustomAlertDialogBuilder(this, themeValue)
|
||||
.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) {_, _ ->
|
||||
items[i].dstPath = testDstPath
|
||||
items[i].overwriteConfirmed = true
|
||||
@ -410,17 +412,17 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
||||
}
|
||||
.setNegativeButton(R.string.no) { _, _ ->
|
||||
with(EditTextDialog(this, R.string.enter_new_name) {
|
||||
items[i].dstPath = PathUtils.pathJoin(dstDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, items[i].explorerElement.parentPath), it)
|
||||
if (items[i].explorerElement.isDirectory){
|
||||
items[i].dstPath = PathUtils.pathJoin(dstDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, items[i].parentPath), it)
|
||||
if (items[i].isDirectory) {
|
||||
for (j in 0 until items.size){
|
||||
if (PathUtils.isChildOf(items[j].explorerElement.fullPath, items[i].explorerElement.fullPath)){
|
||||
items[j].dstPath = PathUtils.pathJoin(items[i].dstPath!!, PathUtils.getRelativePath(items[i].explorerElement.fullPath, items[j].explorerElement.fullPath))
|
||||
if (PathUtils.isChildOf(items[j].srcPath, items[i].srcPath)) {
|
||||
items[j].dstPath = PathUtils.pathJoin(items[i].dstPath!!, PathUtils.getRelativePath(items[i].srcPath, items[j].srcPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
checkPathOverwrite(items, dstDirectoryPath, callback)
|
||||
}) {
|
||||
setSelectedText(items[i].explorerElement.name)
|
||||
setSelectedText(items[i].name)
|
||||
setOnCancelListener{
|
||||
callback(null)
|
||||
}
|
||||
@ -452,7 +454,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
||||
items.clear()
|
||||
break
|
||||
} else {
|
||||
items.add(OperationFile.fromExplorerElement(ExplorerElement(fileName, 1, parentPath = currentDirectoryPath)))
|
||||
items.add(OperationFile(PathUtils.pathJoin(fileName, currentDirectoryPath), Stat.S_IFREG))
|
||||
}
|
||||
}
|
||||
if (items.size > 0) {
|
||||
@ -475,7 +477,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
||||
if (new_name.isEmpty()) {
|
||||
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
|
||||
} 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)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(getString(R.string.rename_failed, old_name))
|
||||
@ -587,8 +589,8 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
||||
}
|
||||
|
||||
protected open fun closeVolumeOnDestroy() {
|
||||
if (!gocryptfsVolume.isClosed()){
|
||||
gocryptfsVolume.close()
|
||||
if (!encryptedVolume.isClosed()){
|
||||
encryptedVolume.close()
|
||||
}
|
||||
RestrictedFileProvider.wipeAll(this) //additional security
|
||||
}
|
||||
@ -616,7 +618,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
||||
if (isCreating){
|
||||
isCreating = false
|
||||
} else {
|
||||
if (gocryptfsVolume.isClosed()){
|
||||
if (encryptedVolume.isClosed()){
|
||||
finish()
|
||||
} else {
|
||||
isStartingActivity = false
|
||||
|
@ -3,6 +3,7 @@ package sushi.hardcore.droidfs.explorers
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
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.databinding.ActivityExplorerBinding
|
||||
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.widgets.CustomAlertDialogBuilder
|
||||
import sushi.hardcore.droidfs.widgets.EditTextDialog
|
||||
@ -36,8 +39,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
private val pickFromOtherVolumes = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
result.data?.let { resultIntent ->
|
||||
val remoteSessionID = resultIntent.getIntExtra("sessionID", -1)
|
||||
val remoteGocryptfsVolume = GocryptfsVolume(applicationContext, remoteSessionID)
|
||||
val remoteEncryptedVolume = resultIntent.getParcelableExtra<EncryptedVolume>("volume")!!
|
||||
val path = resultIntent.getStringExtra("path")
|
||||
val operationFiles = ArrayList<OperationFile>()
|
||||
if (path == null){ //multiples elements
|
||||
@ -46,12 +48,10 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
if (types != null && paths != null){
|
||||
for (i in paths.indices) {
|
||||
operationFiles.add(
|
||||
OperationFile.fromExplorerElement(
|
||||
ExplorerElement(File(paths[i]).name, types[i].toShort(), parentPath = PathUtils.getParentPath(paths[i]))
|
||||
OperationFile(paths[i], types[i])
|
||||
)
|
||||
)
|
||||
if (types[i] == 0){ //directory
|
||||
remoteGocryptfsVolume.recursiveMapFiles(paths[i]).forEach {
|
||||
if (types[i] == Stat.S_IFDIR) {
|
||||
remoteEncryptedVolume.recursiveMapFiles(paths[i])?.forEach {
|
||||
operationFiles.add(OperationFile.fromExplorerElement(it))
|
||||
}
|
||||
}
|
||||
@ -59,18 +59,16 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
}
|
||||
} else {
|
||||
operationFiles.add(
|
||||
OperationFile.fromExplorerElement(
|
||||
ExplorerElement(File(path).name, 1, parentPath = PathUtils.getParentPath(path))
|
||||
)
|
||||
OperationFile(path, Stat.S_IFREG)
|
||||
)
|
||||
}
|
||||
if (operationFiles.size > 0){
|
||||
checkPathOverwrite(operationFiles, currentDirectoryPath) { items ->
|
||||
if (items == null) {
|
||||
remoteGocryptfsVolume.close()
|
||||
remoteEncryptedVolume.close()
|
||||
} else {
|
||||
lifecycleScope.launch {
|
||||
val failedItem = fileOperationService.copyElements(items, remoteGocryptfsVolume)
|
||||
val failedItem = fileOperationService.copyElements(items, remoteEncryptedVolume)
|
||||
if (failedItem == null) {
|
||||
Toast.makeText(this@ExplorerActivity, R.string.success_import, Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
@ -81,12 +79,12 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
.show()
|
||||
}
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
remoteGocryptfsVolume.close()
|
||||
remoteEncryptedVolume.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
remoteGocryptfsVolume.close()
|
||||
remoteEncryptedVolume.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -120,7 +118,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
private val pickImportDirectory = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { rootUri ->
|
||||
rootUri?.let {
|
||||
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 ->
|
||||
checkedOperation?.let {
|
||||
lifecycleScope.launch {
|
||||
@ -175,7 +173,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
setContentView(binding.root)
|
||||
binding.fab.setOnClickListener {
|
||||
if (currentItemAction != ItemsActions.NONE){
|
||||
openDialogCreateFolder()
|
||||
//openDialogCreateFolder()
|
||||
} else {
|
||||
val adapter = IconTextDialogAdapter(this)
|
||||
adapter.items = listOf(
|
||||
@ -192,7 +190,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
"importFromOtherVolumes" -> {
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
intent.action = "pick"
|
||||
intent.putExtra("sessionID", gocryptfsVolume.sessionID)
|
||||
intent.putExtra("volume", encryptedVolume)
|
||||
isStartingActivity = true
|
||||
pickFromOtherVolumes.launch(intent)
|
||||
}
|
||||
@ -215,7 +213,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
"camera" -> {
|
||||
val intent = Intent(this, CameraActivity::class.java)
|
||||
intent.putExtra("path", currentDirectoryPath)
|
||||
intent.putExtra("sessionID", gocryptfsVolume.sessionID)
|
||||
intent.putExtra("volume", encryptedVolume)
|
||||
isStartingActivity = true
|
||||
startActivity(intent)
|
||||
}
|
||||
@ -241,15 +239,15 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
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)
|
||||
if (handleID == -1) {
|
||||
val handleID = encryptedVolume.openFile(filePath)
|
||||
if (handleID == -1L) {
|
||||
CustomAlertDialogBuilder(this, themeValue)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(R.string.file_creation_failed)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
} else {
|
||||
gocryptfsVolume.closeFile(handleID)
|
||||
encryptedVolume.closeFile(handleID)
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
@ -312,7 +310,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
for (i in explorerAdapter.selectedItems){
|
||||
itemsToProcess.add(OperationFile.fromExplorerElement(explorerElements[i]))
|
||||
if (explorerElements[i].isDirectory){
|
||||
gocryptfsVolume.recursiveMapFiles(explorerElements[i].fullPath).forEach {
|
||||
encryptedVolume.recursiveMapFiles(explorerElements[i].fullPath)?.forEach {
|
||||
itemsToProcess.add(OperationFile.fromExplorerElement(it))
|
||||
}
|
||||
}
|
||||
@ -346,11 +344,11 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
}
|
||||
} else if (currentItemAction == ItemsActions.MOVE){
|
||||
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
|
||||
}
|
||||
val toMove = ArrayList<OperationFile>(itemsToProcess.size)
|
||||
val toClean = ArrayList<ExplorerElement>()
|
||||
val toClean = ArrayList<String>()
|
||||
prepareFilesForMove(
|
||||
itemsToProcess,
|
||||
toMove,
|
||||
@ -398,7 +396,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
paths.add(explorerElements[i].fullPath)
|
||||
}
|
||||
isStartingActivity = true
|
||||
ExternalProvider.share(this, themeValue, gocryptfsVolume, paths)
|
||||
ExternalProvider.share(this, themeValue, encryptedVolume, paths)
|
||||
unselectAll()
|
||||
true
|
||||
}
|
||||
@ -418,12 +416,12 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
*/
|
||||
private fun checkMoveOverwrite(items: List<OperationFile>, callback: (List<OperationFile>?) -> Unit) {
|
||||
for (item in items) {
|
||||
if (gocryptfsVolume.pathExists(item.dstPath!!) && !item.overwriteConfirmed) {
|
||||
if (encryptedVolume.pathExists(item.dstPath!!) && !item.overwriteConfirmed) {
|
||||
CustomAlertDialogBuilder(this, themeValue)
|
||||
.setTitle(R.string.warning)
|
||||
.setMessage(
|
||||
getString(
|
||||
if (item.explorerElement.isDirectory) {
|
||||
if (item.isDirectory) {
|
||||
R.string.dir_overwrite_question
|
||||
} else {
|
||||
R.string.file_overwrite_question
|
||||
@ -440,7 +438,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
item.dstPath = PathUtils.pathJoin(PathUtils.getParentPath(item.dstPath!!), it)
|
||||
checkMoveOverwrite(items, callback)
|
||||
}) {
|
||||
setSelectedText(item.explorerElement.name)
|
||||
setSelectedText(item.name)
|
||||
show()
|
||||
}
|
||||
}
|
||||
@ -463,24 +461,24 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
private fun prepareFilesForMove(
|
||||
items: List<OperationFile>,
|
||||
toMove: ArrayList<OperationFile>,
|
||||
toClean: ArrayList<ExplorerElement>,
|
||||
toClean: ArrayList<String>,
|
||||
onReady: () -> Unit
|
||||
) {
|
||||
checkMoveOverwrite(items) { checkedItems ->
|
||||
checkedItems?.let {
|
||||
for (item in checkedItems) {
|
||||
if (!item.overwriteConfirmed || !item.explorerElement.isDirectory) {
|
||||
if (!item.overwriteConfirmed || !item.isDirectory) {
|
||||
toMove.add(item)
|
||||
}
|
||||
}
|
||||
val toCheck = mutableListOf<OperationFile>()
|
||||
for (item in checkedItems) {
|
||||
if (item.overwriteConfirmed && item.explorerElement.isDirectory) {
|
||||
val children = gocryptfsVolume.listDir(item.explorerElement.fullPath)
|
||||
toCheck.addAll(children.map {
|
||||
OperationFile(it, PathUtils.pathJoin(item.dstPath!!, it.name))
|
||||
})
|
||||
toClean.add(item.explorerElement)
|
||||
if (item.overwriteConfirmed && item.isDirectory) {
|
||||
val children = encryptedVolume.readDir(item.srcPath)
|
||||
children?.map {
|
||||
OperationFile(it.fullPath, it.stat.type, PathUtils.pathJoin(item.dstPath!!, it.name))
|
||||
}?.let { toCheck.addAll(it) }
|
||||
toClean.add(item.srcPath)
|
||||
}
|
||||
}
|
||||
if (toCheck.isEmpty()) {
|
||||
@ -514,10 +512,10 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
val element = explorerAdapter.explorerElements[i]
|
||||
val fullPath = PathUtils.pathJoin(currentDirectoryPath, element.name)
|
||||
if (element.isDirectory) {
|
||||
val result = gocryptfsVolume.recursiveRemoveDirectory(fullPath)
|
||||
val result = encryptedVolume.recursiveRemoveDirectory(fullPath)
|
||||
result?.let{ failedItem = it }
|
||||
} else {
|
||||
if (!gocryptfsVolume.removeFile(fullPath)) {
|
||||
if (!encryptedVolume.deleteFile(fullPath)) {
|
||||
failedItem = fullPath
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,12 @@ package sushi.hardcore.droidfs.explorers
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import sushi.hardcore.droidfs.R
|
||||
import sushi.hardcore.droidfs.GocryptfsVolume
|
||||
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
import java.util.*
|
||||
|
||||
@ -14,7 +16,7 @@ class ExplorerActivityPick : BaseExplorerActivity() {
|
||||
private var isFinishingIntentionally = false
|
||||
override fun init() {
|
||||
super.init()
|
||||
resultIntent.putExtra("sessionID", gocryptfsVolume.sessionID)
|
||||
resultIntent.putExtra("volume", encryptedVolume)
|
||||
}
|
||||
|
||||
override fun bindFileOperationService() {
|
||||
@ -65,7 +67,7 @@ class ExplorerActivityPick : BaseExplorerActivity() {
|
||||
for (i in explorerAdapter.selectedItems) {
|
||||
val e = explorerElements[i]
|
||||
paths.add(PathUtils.pathJoin(currentDirectoryPath, e.name))
|
||||
types.add(e.elementType.toInt())
|
||||
types.add(e.stat.type)
|
||||
}
|
||||
resultIntent.putStringArrayListExtra("paths", paths)
|
||||
resultIntent.putIntegerArrayListExtra("types", types)
|
||||
@ -84,10 +86,7 @@ class ExplorerActivityPick : BaseExplorerActivity() {
|
||||
|
||||
override fun closeVolumeOnDestroy() {
|
||||
if (!isFinishingIntentionally && !usf_keep_open){
|
||||
val sessionID = intent.getIntExtra("originalSessionID", -1)
|
||||
if (sessionID != -1){
|
||||
GocryptfsVolume(applicationContext, sessionID).close()
|
||||
}
|
||||
intent.getParcelableExtra<EncryptedVolume>("destinationVolume")?.let { it.close() }
|
||||
super.closeVolumeOnDestroy()
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +1,31 @@
|
||||
package sushi.hardcore.droidfs.explorers
|
||||
|
||||
import sushi.hardcore.droidfs.collation.getCollationKeyForFileName
|
||||
import sushi.hardcore.droidfs.filesystems.Stat
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
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) {
|
||||
val mTime = Date((mTime * 1000).toString().toLong())
|
||||
class ExplorerElement(val name: String, val stat: Stat, val parentPath: String) {
|
||||
val fullPath: String = PathUtils.pathJoin(parentPath, name)
|
||||
val collationKey = Collator.getInstance().getCollationKeyForFileName(fullPath)
|
||||
|
||||
val isDirectory: Boolean
|
||||
get() = elementType.toInt() == DIRECTORY_TYPE
|
||||
|
||||
val isParentFolder: Boolean
|
||||
get() = elementType.toInt() == PARENT_FOLDER_TYPE
|
||||
get() = stat.type == Stat.S_IFDIR
|
||||
|
||||
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 {
|
||||
const val DIRECTORY_TYPE = 0
|
||||
const val PARENT_FOLDER_TYPE = -1
|
||||
const val REGULAR_FILE_TYPE = 1
|
||||
|
||||
@JvmStatic
|
||||
//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 {
|
||||
return ExplorerElement(name, elementType, size, mTime, parentPath)
|
||||
fun new(name: String, elementType: Int, size: Long, mTime: Long, parentPath: String): ExplorerElement {
|
||||
return ExplorerElement(name, Stat(elementType, size, mTime*1000), parentPath)
|
||||
}
|
||||
|
||||
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" -> {
|
||||
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" -> {
|
||||
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" -> {
|
||||
@ -76,12 +74,12 @@ class ExplorerElement(val name: String, val elementType: Short, var size: Long =
|
||||
}
|
||||
"size_desc" -> {
|
||||
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" -> {
|
||||
explorerElements.sortWith { a, b ->
|
||||
doSort(a, b, foldersFirst) { b.mTime.compareTo(a.mTime) }
|
||||
doSort(a, b, foldersFirst) { b.stat.mTime.compareTo(a.stat.mTime) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import kotlinx.coroutines.*
|
||||
import sushi.hardcore.droidfs.GocryptfsVolume
|
||||
import sushi.hardcore.droidfs.R
|
||||
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
||||
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
import sushi.hardcore.droidfs.util.Wiper
|
||||
import java.io.File
|
||||
@ -29,15 +30,15 @@ class FileOperationService : Service() {
|
||||
}
|
||||
|
||||
private val binder = LocalBinder()
|
||||
private lateinit var gocryptfsVolume: GocryptfsVolume
|
||||
private lateinit var encryptedVolume: EncryptedVolume
|
||||
private lateinit var notificationManager: NotificationManagerCompat
|
||||
private val tasks = HashMap<Int, Job>()
|
||||
private var lastNotificationId = 0
|
||||
|
||||
inner class LocalBinder : Binder() {
|
||||
fun getService(): FileOperationService = this@FileOperationService
|
||||
fun setGocryptfsVolume(g: GocryptfsVolume) {
|
||||
gocryptfsVolume = g
|
||||
fun setEncryptedVolume(volume: EncryptedVolume) {
|
||||
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
|
||||
val srcHandleId = remoteGocryptfsVolume.openReadMode(srcPath)
|
||||
if (srcHandleId != -1){
|
||||
val dstHandleId = gocryptfsVolume.openWriteMode(dstPath)
|
||||
if (dstHandleId != -1){
|
||||
val srcFileHandle = remoteEncryptedVolume.openFile(srcPath)
|
||||
if (srcFileHandle != -1L) {
|
||||
val dstFileHandle = encryptedVolume.openFile(dstPath)
|
||||
if (dstFileHandle != -1L) {
|
||||
var offset: Long = 0
|
||||
val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS)
|
||||
var length: Int
|
||||
while (remoteGocryptfsVolume.readFile(srcHandleId, offset, ioBuffer).also { length = it } > 0) {
|
||||
val written = gocryptfsVolume.writeFile(dstHandleId, offset, ioBuffer, length).toLong()
|
||||
while (remoteEncryptedVolume.read(srcFileHandle, ioBuffer, offset).also { length = it } > 0) {
|
||||
val written = encryptedVolume.write(dstFileHandle, offset, ioBuffer, length).toLong()
|
||||
if (written == length.toLong()) {
|
||||
offset += written
|
||||
} else {
|
||||
@ -139,11 +140,11 @@ class FileOperationService : Service() {
|
||||
break
|
||||
}
|
||||
}
|
||||
gocryptfsVolume.closeFile(dstHandleId)
|
||||
encryptedVolume.closeFile(dstFileHandle)
|
||||
} else {
|
||||
success = false
|
||||
}
|
||||
remoteGocryptfsVolume.closeFile(srcHandleId)
|
||||
remoteEncryptedVolume.closeFile(srcFileHandle)
|
||||
} else {
|
||||
success = false
|
||||
}
|
||||
@ -152,22 +153,22 @@ class FileOperationService : Service() {
|
||||
|
||||
suspend fun copyElements(
|
||||
items: ArrayList<OperationFile>,
|
||||
remoteGocryptfsVolume: GocryptfsVolume = gocryptfsVolume
|
||||
remoteEncryptedVolume: EncryptedVolume = encryptedVolume
|
||||
): String? = coroutineScope {
|
||||
val notification = showNotification(R.string.file_op_copy_msg, items.size)
|
||||
val task = async {
|
||||
var failedItem: String? = null
|
||||
for (i in 0 until items.size) {
|
||||
withContext(Dispatchers.IO) {
|
||||
if (items[i].explorerElement.isDirectory) {
|
||||
if (!gocryptfsVolume.pathExists(items[i].dstPath!!)) {
|
||||
if (!gocryptfsVolume.mkdir(items[i].dstPath!!)) {
|
||||
failedItem = items[i].explorerElement.fullPath
|
||||
if (items[i].isDirectory) {
|
||||
if (!encryptedVolume.pathExists(items[i].dstPath!!)) {
|
||||
if (!encryptedVolume.mkdir(items[i].dstPath!!)) {
|
||||
failedItem = items[i].srcPath
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!copyFile(items[i].explorerElement.fullPath, items[i].dstPath!!, remoteGocryptfsVolume)) {
|
||||
failedItem = items[i].explorerElement.fullPath
|
||||
if (!copyFile(items[i].srcPath, items[i].dstPath!!, remoteEncryptedVolume)) {
|
||||
failedItem = items[i].srcPath
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -183,23 +184,23 @@ class FileOperationService : Service() {
|
||||
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 task = async(Dispatchers.IO) {
|
||||
val total = toMove.size+toClean.size
|
||||
var failedItem: String? = null
|
||||
for ((i, item) in toMove.withIndex()) {
|
||||
if (!gocryptfsVolume.rename(item.explorerElement.fullPath, item.dstPath!!)) {
|
||||
failedItem = item.explorerElement.fullPath
|
||||
if (!encryptedVolume.rename(item.srcPath, item.dstPath!!)) {
|
||||
failedItem = item.srcPath
|
||||
break
|
||||
} else {
|
||||
updateNotificationProgress(notification, i+1, total)
|
||||
}
|
||||
}
|
||||
if (failedItem == null) {
|
||||
for ((i, folder) in toClean.asReversed().withIndex()) {
|
||||
if (!gocryptfsVolume.rmdir(folder.fullPath)) {
|
||||
failedItem = folder.fullPath
|
||||
for ((i, folderPath) in toClean.asReversed().withIndex()) {
|
||||
if (!encryptedVolume.rmdir(folderPath)) {
|
||||
failedItem = folderPath
|
||||
break
|
||||
} else {
|
||||
updateNotificationProgress(notification, toMove.size+i+1, total)
|
||||
@ -221,7 +222,7 @@ class FileOperationService : Service() {
|
||||
for (i in dstPaths.indices) {
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
if (!gocryptfsVolume.importFile(this@FileOperationService, uris[i], dstPaths[i])) {
|
||||
if (!encryptedVolume.importFile(this@FileOperationService, uris[i], dstPaths[i])) {
|
||||
failedIndex = i
|
||||
}
|
||||
} catch (e: FileNotFoundException) {
|
||||
@ -301,7 +302,7 @@ class FileOperationService : Service() {
|
||||
|
||||
// create destination folders so the new files can use them
|
||||
for (dir in dstDirs) {
|
||||
if (!gocryptfsVolume.mkdir(dir)) {
|
||||
if (!encryptedVolume.mkdir(dir)) {
|
||||
failedItem = dir
|
||||
break
|
||||
}
|
||||
@ -345,7 +346,7 @@ class FileOperationService : Service() {
|
||||
return if (outputStream == null) {
|
||||
false
|
||||
} else {
|
||||
gocryptfsVolume.exportFile(srcPath, outputStream)
|
||||
encryptedVolume.exportFile(srcPath, outputStream)
|
||||
}
|
||||
}
|
||||
|
||||
@ -355,7 +356,7 @@ class FileOperationService : Service() {
|
||||
scope: CoroutineScope
|
||||
): String? {
|
||||
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) {
|
||||
if (!scope.isActive) {
|
||||
return null
|
||||
|
@ -1,11 +1,22 @@
|
||||
package sushi.hardcore.droidfs.file_operations
|
||||
|
||||
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 {
|
||||
fun fromExplorerElement(e: ExplorerElement): OperationFile {
|
||||
return OperationFile(e, null)
|
||||
return OperationFile(e.fullPath, e.stat.type)
|
||||
}
|
||||
}
|
||||
}
|
@ -5,18 +5,18 @@ import com.google.android.exoplayer2.upstream.DataSource
|
||||
import com.google.android.exoplayer2.upstream.DataSpec
|
||||
import com.google.android.exoplayer2.upstream.TransferListener
|
||||
import sushi.hardcore.droidfs.ConstValues
|
||||
import sushi.hardcore.droidfs.GocryptfsVolume
|
||||
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.min
|
||||
|
||||
class GocryptfsDataSource(private val gocryptfsVolume: GocryptfsVolume, private val filePath: String): DataSource {
|
||||
private var handleID = -1
|
||||
class EncryptedVolumeDataSource(private val encryptedVolume: EncryptedVolume, private val filePath: String): DataSource {
|
||||
private var fileHandle = -1L
|
||||
private var fileSize: Long = -1
|
||||
private var fileOffset: Long = 0
|
||||
override fun open(dataSpec: DataSpec): Long {
|
||||
fileOffset = dataSpec.position
|
||||
handleID = gocryptfsVolume.openReadMode(filePath)
|
||||
fileSize = gocryptfsVolume.getSize(filePath)
|
||||
fileHandle = encryptedVolume.openFile(filePath)
|
||||
fileSize = encryptedVolume.getAttr(filePath)!!.size
|
||||
return fileSize
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ class GocryptfsDataSource(private val gocryptfsVolume: GocryptfsVolume, private
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
gocryptfsVolume.closeFile(handleID)
|
||||
encryptedVolume.closeFile(fileHandle)
|
||||
}
|
||||
|
||||
override fun addTransferListener(transferListener: TransferListener) {
|
||||
@ -44,7 +44,7 @@ class GocryptfsDataSource(private val gocryptfsVolume: GocryptfsVolume, private
|
||||
} else {
|
||||
ByteArray(tmpReadLength)
|
||||
}
|
||||
val read = gocryptfsVolume.readFile(handleID, fileOffset, tmpBuff)
|
||||
val read = encryptedVolume.read(fileHandle, tmpBuff, fileOffset)
|
||||
System.arraycopy(tmpBuff, 0, buffer, offset+totalRead, read)
|
||||
fileOffset += read
|
||||
totalRead += read
|
||||
@ -52,9 +52,9 @@ class GocryptfsDataSource(private val gocryptfsVolume: GocryptfsVolume, private
|
||||
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 {
|
||||
return GocryptfsDataSource(gocryptfsVolume, filePath)
|
||||
return EncryptedVolumeDataSource(encryptedVolume, filePath)
|
||||
}
|
||||
}
|
||||
}
|
@ -10,11 +10,12 @@ import sushi.hardcore.droidfs.GocryptfsVolume
|
||||
import sushi.hardcore.droidfs.R
|
||||
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
|
||||
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
||||
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||
|
||||
abstract class FileViewerActivity: BaseActivity() {
|
||||
protected lateinit var gocryptfsVolume: GocryptfsVolume
|
||||
protected lateinit var encryptedVolume: EncryptedVolume
|
||||
protected lateinit var filePath: String
|
||||
private lateinit var originalParentPath: String
|
||||
private lateinit var windowInsetsController: WindowInsetsControllerCompat
|
||||
@ -33,8 +34,7 @@ abstract class FileViewerActivity: BaseActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
filePath = intent.getStringExtra("path")!!
|
||||
originalParentPath = PathUtils.getParentPath(filePath)
|
||||
val sessionID = intent.getIntExtra("sessionID", -1)
|
||||
gocryptfsVolume = GocryptfsVolume(applicationContext, sessionID)
|
||||
encryptedVolume = intent.getParcelableExtra("volume")!!
|
||||
usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false)
|
||||
foldersFirst = sharedPrefs.getBoolean("folders_first", true)
|
||||
windowInsetsController = WindowInsetsControllerCompat(window, window.decorView)
|
||||
@ -67,7 +67,7 @@ abstract class FileViewerActivity: BaseActivity() {
|
||||
}
|
||||
|
||||
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) {
|
||||
val dialog = CustomAlertDialogBuilder(this, themeValue)
|
||||
.setTitle(R.string.error)
|
||||
@ -85,13 +85,15 @@ abstract class FileViewerActivity: BaseActivity() {
|
||||
|
||||
protected fun createPlaylist() {
|
||||
if (!wasMapped){
|
||||
for (e in gocryptfsVolume.recursiveMapFiles(originalParentPath)) {
|
||||
encryptedVolume.recursiveMapFiles(originalParentPath)?.let { elements ->
|
||||
for (e in elements) {
|
||||
if (e.isRegularFile) {
|
||||
if (ConstValues.isExtensionType(getFileType(), e.name) || filePath == e.fullPath) {
|
||||
mappedPlaylist.add(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val sortOrder = intent.getStringExtra("sortOrder") ?: "name"
|
||||
ExplorerElement.sortBy(sortOrder, foldersFirst, mappedPlaylist)
|
||||
//find current index
|
||||
@ -133,7 +135,7 @@ abstract class FileViewerActivity: BaseActivity() {
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (!isFinishingIntentionally) {
|
||||
gocryptfsVolume.close()
|
||||
encryptedVolume.close()
|
||||
RestrictedFileProvider.wipeAll(this)
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ class ImageViewer: FileViewerActivity() {
|
||||
.setTitle(R.string.warning)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
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)
|
||||
refreshPlaylist()
|
||||
if (mappedPlaylist.size == 0) { //deleted all images of the playlist
|
||||
@ -275,7 +275,7 @@ class ImageViewer: FileViewerActivity() {
|
||||
Bitmap.CompressFormat.JPEG
|
||||
}, 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()
|
||||
callback()
|
||||
} else {
|
||||
|
@ -25,7 +25,7 @@ abstract class MediaPlayer: FileViewerActivity() {
|
||||
protected open fun onVideoSizeChanged(width: Int, height: Int) {}
|
||||
|
||||
private fun createMediaSource(filePath: String): MediaSource {
|
||||
val dataSourceFactory = GocryptfsDataSource.Factory(gocryptfsVolume, filePath)
|
||||
val dataSourceFactory = EncryptedVolumeDataSource.Factory(encryptedVolume, filePath)
|
||||
return ProgressiveMediaSource.Factory(dataSourceFactory, DefaultExtractorsFactory())
|
||||
.createMediaSource(MediaItem.fromUri(ConstValues.FAKE_URI))
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ class PdfViewer: FileViewerActivity() {
|
||||
pdfViewer = PdfViewer(this)
|
||||
val fileName = File(filePath).name
|
||||
title = fileName
|
||||
val fileSize = gocryptfsVolume.getSize(filePath)
|
||||
val fileSize = encryptedVolume.getAttr(filePath)?.size
|
||||
loadWholeFile(filePath, fileSize)?.let {
|
||||
pdfViewer.loadPdf(ByteArrayInputStream(it), fileName, fileSize)
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package sushi.hardcore.droidfs.file_viewers
|
||||
import android.annotation.SuppressLint
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.EditText
|
||||
@ -68,14 +69,14 @@ class TextEditor: FileViewerActivity() {
|
||||
private fun save(): Boolean{
|
||||
var success = false
|
||||
val content = editor.text.toString().toByteArray()
|
||||
val handleID = gocryptfsVolume.openWriteMode(filePath)
|
||||
if (handleID != -1){
|
||||
val fileHandle = encryptedVolume.openFile(filePath)
|
||||
if (fileHandle != -1L) {
|
||||
val buff = ByteArrayInputStream(content)
|
||||
var offset: Long = 0
|
||||
val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS)
|
||||
var length: Int
|
||||
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()) {
|
||||
offset += written
|
||||
} else {
|
||||
@ -83,9 +84,9 @@ class TextEditor: FileViewerActivity() {
|
||||
}
|
||||
}
|
||||
if (offset == content.size.toLong()){
|
||||
success = gocryptfsVolume.truncate(handleID, offset)
|
||||
success = encryptedVolume.truncate(filePath, offset)
|
||||
}
|
||||
gocryptfsVolume.closeFile(handleID)
|
||||
encryptedVolume.closeFile(fileHandle)
|
||||
buff.close()
|
||||
}
|
||||
if (success){
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
14
app/src/main/java/sushi/hardcore/droidfs/filesystems/Stat.kt
Normal file
14
app/src/main/java/sushi/hardcore/droidfs/filesystems/Stat.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import android.net.Uri
|
||||
import android.os.storage.StorageManager
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.OpenableColumns
|
||||
import android.util.Log
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.core.content.ContextCompat
|
||||
import sushi.hardcore.droidfs.R
|
||||
@ -19,16 +20,16 @@ object PathUtils {
|
||||
fun getParentPath(path: String): String {
|
||||
return if (path.endsWith("/")) {
|
||||
val a = path.substring(0, path.length - 2)
|
||||
if (a.contains("/")) {
|
||||
a.substring(0, a.lastIndexOf("/"))
|
||||
if (a.count { it == '/' } == 1) {
|
||||
"/"
|
||||
} else {
|
||||
""
|
||||
a.substring(0, a.lastIndexOf("/"))
|
||||
}
|
||||
} else {
|
||||
if (path.contains("/")) {
|
||||
path.substring(0, path.lastIndexOf("/"))
|
||||
if (path.count { it == '/' } == 1) {
|
||||
"/"
|
||||
} else {
|
||||
""
|
||||
path.substring(0, path.lastIndexOf("/"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -78,16 +78,15 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_createVolume(JNIEnv *
|
||||
}
|
||||
|
||||
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,
|
||||
jcharArray jpassword,
|
||||
jbyteArray jpassword,
|
||||
jbyteArray jgiven_hash,
|
||||
jbyteArray jreturned_hash) {
|
||||
const char* root_cipher_dir = (*env)->GetStringUTFChars(env, jroot_cipher_dir, NULL);
|
||||
GoString go_root_cipher_dir = {root_cipher_dir, strlen(root_cipher_dir)};
|
||||
|
||||
size_t password_len;
|
||||
jchar* jchar_password;
|
||||
char* password;
|
||||
GoSlice go_password = {NULL, 0, 0};
|
||||
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};
|
||||
if ((*env)->IsSameObject(env, jgiven_hash, NULL)){
|
||||
password_len = (*env)->GetArrayLength(env, jpassword);
|
||||
jchar_password = (*env)->GetCharArrayElements(env, jpassword, NULL);
|
||||
password = malloc(password_len);
|
||||
jcharArray_to_charArray(jchar_password, password, password_len);
|
||||
password = (char*)(*env)->GetByteArrayElements(env, jpassword, NULL);
|
||||
go_password.data = password;
|
||||
go_password.len = 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)){
|
||||
wipe(password, password_len);
|
||||
free(password);
|
||||
(*env)->ReleaseCharArrayElements(env, jpassword, jchar_password, 0);
|
||||
(*env)->ReleaseByteArrayElements(env, jpassword, (jbyte*)password, 0);
|
||||
} else {
|
||||
wipe(given_hash, given_hash_len);
|
||||
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");
|
||||
|
||||
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);
|
||||
unsigned int c = 0;
|
||||
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)};
|
||||
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);
|
||||
jobject explorerElement = (*env)->CallStaticObjectMethod(
|
||||
env,
|
||||
classExplorerElement,
|
||||
explorerElement_new,
|
||||
jname,
|
||||
type,
|
||||
(long long) attrs.r0,
|
||||
attrs.r1,
|
||||
elements.r1[i],
|
||||
(long long) attrs.r1,
|
||||
attrs.r2,
|
||||
jplain_dir
|
||||
);
|
||||
(*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;
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1get_1size(JNIEnv *env, jobject thiz,
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1get_1attr(JNIEnv *env, jobject thiz,
|
||||
jint sessionID, jstring jfile_path) {
|
||||
const char* file_path = (*env)->GetStringUTFChars(env, jfile_path, NULL);
|
||||
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);
|
||||
|
||||
return attrs.r0;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1path_1exists(JNIEnv *env, jobject thiz,
|
||||
jint sessionID,
|
||||
jstring jfile_path) {
|
||||
const char* file_path = (*env)->GetStringUTFChars(env, jfile_path, NULL);
|
||||
GoString go_file_path = {file_path, strlen(file_path)};
|
||||
|
||||
struct gcf_get_attrs_return attrs = gcf_get_attrs(sessionID, go_file_path);
|
||||
|
||||
(*env)->ReleaseStringUTFChars(env, jfile_path, file_path);
|
||||
|
||||
return attrs.r1 != 0;
|
||||
if (attrs.r3 == 1) {
|
||||
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)attrs.r0,
|
||||
(jlong)attrs.r1,
|
||||
(jlong)attrs.r2
|
||||
);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
@ -396,8 +385,15 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1read_1file(JNIEnv *env, jobj
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1truncate(JNIEnv *env, jobject thiz,
|
||||
jint sessionID,
|
||||
jint handleID, jlong offset) {
|
||||
return gcf_truncate(sessionID, handleID, offset);
|
||||
jstring jpath,
|
||||
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
|
||||
|
198
app/src/main/native/libcryfs.c
Normal file
198
app/src/main/native/libcryfs.c
Normal 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);
|
||||
}
|
@ -39,7 +39,7 @@
|
||||
<string name="external_open">فتح بتطبيق خارجي</string>
|
||||
<string name="single_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="import_from_other_volume">إستيراد من مجلد مشفر آخر</string>
|
||||
<string name="read_file_failed">لقد فشل فتح هذا الملف.</string>
|
||||
|
@ -39,7 +39,7 @@
|
||||
<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="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="import_from_other_volume">Importar desde otro volumen</string>
|
||||
<string name="read_file_failed">No se ha podido abrir este archivo.</string>
|
||||
|
@ -39,7 +39,7 @@
|
||||
<string name="external_open">Abrir com app externo</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="location">Localização: /%s</string>
|
||||
<string name="location">Localização: %s</string>
|
||||
<string name="total_size">Tamanho total: %s</string>
|
||||
<string name="import_from_other_volume">Importar do outro volume</string>
|
||||
<string name="read_file_failed">Falha ao abrir este arquivo.</string>
|
||||
|
@ -37,7 +37,7 @@
|
||||
<string name="external_open">Открыть внешним приложением</string>
|
||||
<string name="single_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="import_from_other_volume">Импорт из другого тома</string>
|
||||
<string name="read_file_failed">Невозможно открыть файл.</string>
|
||||
|
@ -39,7 +39,7 @@
|
||||
<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="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="import_from_other_volume">Import from another volume</string>
|
||||
<string name="read_file_failed">Failed to open this file.</string>
|
||||
|
Loading…
Reference in New Issue
Block a user