From dbd04848bd00b5fda04a89c2808f2b6f3ac0b5c3 Mon Sep 17 00:00:00 2001 From: Hardcore Sushi Date: Sat, 18 Jun 2022 21:13:16 +0200 Subject: [PATCH] libcryfs genesis --- app/CMakeLists.txt | 6 + app/build.gradle | 2 +- .../sushi/hardcore/droidfs/CameraActivity.kt | 21 +- .../droidfs/ChangePasswordActivity.kt | 2 +- .../sushi/hardcore/droidfs/ConstValues.kt | 2 - .../hardcore/droidfs/FingerprintProtector.kt | 4 +- .../sushi/hardcore/droidfs/GocryptfsVolume.kt | 241 ++++-------------- .../sushi/hardcore/droidfs/MainActivity.kt | 86 ++++--- .../droidfs/{Volume.kt => SavedVolume.kt} | 20 +- .../sushi/hardcore/droidfs/VolumeDatabase.kt | 93 +++++-- .../adapters/ExplorerElementAdapter.kt | 21 +- .../droidfs/adapters/VolumeAdapter.kt | 10 +- .../add_volume/CreateVolumeFragment.kt | 7 +- .../droidfs/add_volume/SelectPathFragment.kt | 15 +- .../content_providers/ExternalProvider.kt | 14 +- .../droidfs/explorers/BaseExplorerActivity.kt | 64 ++--- .../droidfs/explorers/ExplorerActivity.kt | 74 +++--- .../droidfs/explorers/ExplorerActivityPick.kt | 11 +- .../droidfs/explorers/ExplorerElement.kt | 34 ++- .../file_operations/FileOperationService.kt | 59 ++--- .../droidfs/file_operations/OperationFile.kt | 15 +- ...Source.kt => EncryptedVolumeDataSource.kt} | 18 +- .../file_viewers/FileViewerActivity.kt | 20 +- .../droidfs/file_viewers/ImageViewer.kt | 4 +- .../droidfs/file_viewers/MediaPlayer.kt | 2 +- .../droidfs/file_viewers/PdfViewer.kt | 2 +- .../droidfs/file_viewers/TextEditor.kt | 11 +- .../droidfs/filesystems/CryfsVolume.kt | 99 +++++++ .../droidfs/filesystems/EncryptedVolume.kt | 214 ++++++++++++++++ .../hardcore/droidfs/filesystems/Stat.kt | 14 + .../sushi/hardcore/droidfs/util/PathUtils.kt | 13 +- app/src/main/native/gocryptfs_jni.c | 66 +++-- app/src/main/native/libcryfs.c | 198 ++++++++++++++ app/src/main/res/values-ar/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 38 files changed, 973 insertions(+), 499 deletions(-) rename app/src/main/java/sushi/hardcore/droidfs/{Volume.kt => SavedVolume.kt} (56%) rename app/src/main/java/sushi/hardcore/droidfs/file_viewers/{GocryptfsDataSource.kt => EncryptedVolumeDataSource.kt} (73%) create mode 100644 app/src/main/java/sushi/hardcore/droidfs/filesystems/CryfsVolume.kt create mode 100644 app/src/main/java/sushi/hardcore/droidfs/filesystems/EncryptedVolume.kt create mode 100644 app/src/main/java/sushi/hardcore/droidfs/filesystems/Stat.kt create mode 100644 app/src/main/native/libcryfs.c diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index d9ea853..99ff421 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -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 diff --git a/app/build.gradle b/app/build.gradle index 7d098aa..f26ac38 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 diff --git a/app/src/main/java/sushi/hardcore/droidfs/CameraActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/CameraActivity.kt index 3a77d4e..4375c0d 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/CameraActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/CameraActivity.kt @@ -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 - 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) } } diff --git a/app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt index 5837e33..a73e973 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt @@ -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 diff --git a/app/src/main/java/sushi/hardcore/droidfs/ConstValues.kt b/app/src/main/java/sushi/hardcore/droidfs/ConstValues.kt index fb15ceb..d8f7e55 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/ConstValues.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/ConstValues.kt @@ -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") diff --git a/app/src/main/java/sushi/hardcore/droidfs/FingerprintProtector.kt b/app/src/main/java/sushi/hardcore/droidfs/FingerprintProtector.kt index 1665471..05bb457 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/FingerprintProtector.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/FingerprintProtector.kt @@ -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)) diff --git a/app/src/main/java/sushi/hardcore/droidfs/GocryptfsVolume.kt b/app/src/main/java/sushi/hardcore/droidfs/GocryptfsVolume.kt index e85555e..732f9ad 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/GocryptfsVolume.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/GocryptfsVolume.kt @@ -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 @@ -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? { + 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 { - 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 { - val result = mutableListOf() - 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 { - 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) } } \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt index 770228a..62425ea 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt @@ -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, i: Int = 0, doDeleteVolumeContent: Boolean? = null) { + private fun removeVolumes(volumes: List, 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(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(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(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(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("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) } } diff --git a/app/src/main/java/sushi/hardcore/droidfs/Volume.kt b/app/src/main/java/sushi/hardcore/droidfs/SavedVolume.kt similarity index 56% rename from app/src/main/java/sushi/hardcore/droidfs/Volume.kt rename to app/src/main/java/sushi/hardcore/droidfs/SavedVolume.kt index a6739c8..a0c35d4 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/Volume.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/SavedVolume.kt @@ -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 { - override fun createFromParcel(parcel: Parcel): Volume { - return Volume(parcel) + companion object { + const val VOLUMES_DIRECTORY = "volumes" + + @JvmField + val CREATOR = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel) = SavedVolume(parcel) + override fun newArray(size: Int) = arrayOfNulls(size) } - override fun newArray(size: Int): Array { - return arrayOfNulls(size) + fun getHiddenVolumeFullPath(filesDir: String, name: String): String { + return PathUtils.pathJoin(filesDir, VOLUMES_DIRECTORY, name) } } } \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/VolumeDatabase.kt b/app/src/main/java/sushi/hardcore/droidfs/VolumeDatabase.kt index 14bf791..0948c8d 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/VolumeDatabase.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/VolumeDatabase.kt @@ -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 { - val list: MutableList = ArrayList() + fun getVolumes(): List { + val list: MutableList = 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 ) diff --git a/app/src/main/java/sushi/hardcore/droidfs/adapters/ExplorerElementAdapter.kt b/app/src/main/java/sushi/hardcore/droidfs/adapters/ExplorerElementAdapter.kt index d36f82c..6a3505b 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/adapters/ExplorerElementAdapter.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/adapters/ExplorerElementAdapter.kt @@ -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(listener::onSelectionChanged) { @@ -42,7 +43,7 @@ class ExplorerElementAdapter( private var thumbnailsCache: LruCache? = 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 } } \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/adapters/VolumeAdapter.kt b/app/src/main/java/sushi/hardcore/droidfs/adapters/VolumeAdapter.kt index 1726e39..1d4ba41 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/adapters/VolumeAdapter.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/adapters/VolumeAdapter.kt @@ -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(listener::onSelectionChanged) { +) : SelectableAdapter(listener::onSelectionChanged) { private val inflater: LayoutInflater = LayoutInflater.from(context) - lateinit var volumes: List + lateinit var volumes: List 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 { + override fun getItems(): List { return volumes } diff --git a/app/src/main/java/sushi/hardcore/droidfs/add_volume/CreateVolumeFragment.kt b/app/src/main/java/sushi/hardcore/droidfs/add_volume/CreateVolumeFragment.kt index c0cfd1e..446db9f 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/add_volume/CreateVolumeFragment.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/add_volume/CreateVolumeFragment.kt @@ -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(requireActivity() as AppCompatActivity, themeValue, R.string.loading_msg_create) { - override suspend fun doTask(): Volume? { + object: LoadingTask(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) diff --git a/app/src/main/java/sushi/hardcore/droidfs/add_volume/SelectPathFragment.kt b/app/src/main/java/sushi/hardcore/droidfs/add_volume/SelectPathFragment.kt index 4bde96d..7423c4c 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/add_volume/SelectPathFragment.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/add_volume/SelectPathFragment.kt @@ -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) } } \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/content_providers/ExternalProvider.kt b/app/src/main/java/sushi/hardcore/droidfs/content_providers/ExternalProvider.kt index cee4e32..5717abe 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/content_providers/ExternalProvider.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/content_providers/ExternalProvider.kt @@ -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 { + private fun exportFile(context: Context, encryptedVolume: EncryptedVolume, file_path: String, previous_content_type: String?): Pair { 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) { + fun share(activity: AppCompatActivity, themeValue: String, encryptedVolume: EncryptedVolume, file_paths: List) { var contentType: String? = null val uris = ArrayList(file_paths.size) object : LoadingTask(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(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) diff --git a/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt index eba643e..10fe96e 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt @@ -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, dstDirectoryPath: String, callback: (ArrayList?) -> 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 diff --git a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt index 465f299..6a6257d 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt @@ -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("volume")!! val path = resultIntent.getStringExtra("path") val operationFiles = ArrayList() 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(itemsToProcess.size) - val toClean = ArrayList() + val toClean = ArrayList() 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, callback: (List?) -> 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, toMove: ArrayList, - toClean: ArrayList, + toClean: ArrayList, 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() 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 } } diff --git a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityPick.kt b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityPick.kt index 3f332bd..d289ec2 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityPick.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityPick.kt @@ -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("destinationVolume")?.let { it.close() } super.closeVolumeOnDestroy() } } diff --git a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerElement.kt b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerElement.kt index 8b1fc91..e128d31 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerElement.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerElement.kt @@ -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) } } } } diff --git a/app/src/main/java/sushi/hardcore/droidfs/file_operations/FileOperationService.kt b/app/src/main/java/sushi/hardcore/droidfs/file_operations/FileOperationService.kt index e8a72a8..ed231ae 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/file_operations/FileOperationService.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/file_operations/FileOperationService.kt @@ -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() 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, - 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, toClean: List): String? = coroutineScope { + suspend fun moveElements(toMove: List, toClean: List): 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 diff --git a/app/src/main/java/sushi/hardcore/droidfs/file_operations/OperationFile.kt b/app/src/main/java/sushi/hardcore/droidfs/file_operations/OperationFile.kt index cec8481..76f5e57 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/file_operations/OperationFile.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/file_operations/OperationFile.kt @@ -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) } } } \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/GocryptfsDataSource.kt b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/EncryptedVolumeDataSource.kt similarity index 73% rename from app/src/main/java/sushi/hardcore/droidfs/file_viewers/GocryptfsDataSource.kt rename to app/src/main/java/sushi/hardcore/droidfs/file_viewers/EncryptedVolumeDataSource.kt index 507fe67..0827f2a 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/GocryptfsDataSource.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/EncryptedVolumeDataSource.kt @@ -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) } } } \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/FileViewerActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/FileViewerActivity.kt index e5dedda..ba2b5a1 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/FileViewerActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/FileViewerActivity.kt @@ -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,10 +85,12 @@ abstract class FileViewerActivity: BaseActivity() { protected fun createPlaylist() { if (!wasMapped){ - for (e in gocryptfsVolume.recursiveMapFiles(originalParentPath)) { - if (e.isRegularFile) { - if (ConstValues.isExtensionType(getFileType(), e.name) || filePath == e.fullPath) { - mappedPlaylist.add(e) + encryptedVolume.recursiveMapFiles(originalParentPath)?.let { elements -> + for (e in elements) { + if (e.isRegularFile) { + if (ConstValues.isExtensionType(getFileType(), e.name) || filePath == e.fullPath) { + mappedPlaylist.add(e) + } } } } @@ -133,7 +135,7 @@ abstract class FileViewerActivity: BaseActivity() { override fun onDestroy() { super.onDestroy() if (!isFinishingIntentionally) { - gocryptfsVolume.close() + encryptedVolume.close() RestrictedFileProvider.wipeAll(this) } } diff --git a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/ImageViewer.kt b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/ImageViewer.kt index c8c1e8f..2dfce6f 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/ImageViewer.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/ImageViewer.kt @@ -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 { diff --git a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/MediaPlayer.kt b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/MediaPlayer.kt index 7dc4a1f..352929c 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/MediaPlayer.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/MediaPlayer.kt @@ -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)) } diff --git a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/PdfViewer.kt b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/PdfViewer.kt index 9da796a..8dd4b62 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/PdfViewer.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/PdfViewer.kt @@ -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) } diff --git a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/TextEditor.kt b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/TextEditor.kt index 8085923..65c5da1 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/TextEditor.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/TextEditor.kt @@ -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){ diff --git a/app/src/main/java/sushi/hardcore/droidfs/filesystems/CryfsVolume.kt b/app/src/main/java/sushi/hardcore/droidfs/filesystems/CryfsVolume.kt new file mode 100644 index 0000000..4e0e853 --- /dev/null +++ b/app/src/main/java/sushi/hardcore/droidfs/filesystems/CryfsVolume.kt @@ -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? + 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? { + 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) + } +} \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/filesystems/EncryptedVolume.kt b/app/src/main/java/sushi/hardcore/droidfs/filesystems/EncryptedVolume.kt new file mode 100644 index 0000000..04b5112 --- /dev/null +++ b/app/src/main/java/sushi/hardcore/droidfs/filesystems/EncryptedVolume.kt @@ -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 { + 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(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? + 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 { + 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? { + val result = mutableListOf() + 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 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/filesystems/Stat.kt b/app/src/main/java/sushi/hardcore/droidfs/filesystems/Stat.kt new file mode 100644 index 0000000..e08545f --- /dev/null +++ b/app/src/main/java/sushi/hardcore/droidfs/filesystems/Stat.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/util/PathUtils.kt b/app/src/main/java/sushi/hardcore/droidfs/util/PathUtils.kt index 89646b8..4d98524 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/util/PathUtils.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/util/PathUtils.kt @@ -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("/")) } } } diff --git a/app/src/main/native/gocryptfs_jni.c b/app/src/main/native/gocryptfs_jni.c index a1e1244..08526fb 100644 --- a/app/src/main/native/gocryptfs_jni.c +++ b/app/src/main/native/gocryptfs_jni.c @@ -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; iNewStringUTF(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, "", "(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 diff --git a/app/src/main/native/libcryfs.c b/app/src/main/native/libcryfs.c new file mode 100644 index 0000000..dcbeceb --- /dev/null +++ b/app/src/main/native/libcryfs.c @@ -0,0 +1,198 @@ +#include +#include + +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, "", "()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, "", "(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); +} \ No newline at end of file diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 526c70f..6627024 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -39,7 +39,7 @@ فتح بتطبيق خارجي هل أنت متأكد من حذف %s ? هل أنت متأكد من حذف هذه %s العناصر ? - موقع: /%s + موقع: %s الحجم الكلي: %s إستيراد من مجلد مشفر آخر لقد فشل فتح هذا الملف. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index baab8ea..7d3a4e3 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -39,7 +39,7 @@ Abrir con una aplicación externa ¿Estás seguro de que quieres borrar %s ? ¿Estás seguro de que quiere borrar %s objetos? - Ubicación: /%s + Ubicación: %s Tamaño total: %s Importar desde otro volumen No se ha podido abrir este archivo. diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index f6335b9..f5ffe5d 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -39,7 +39,7 @@ Abrir com app externo Você tem certeza que quer excluir %s? Você realmente deseja excluir estos %s itens? - Localização: /%s + Localização: %s Tamanho total: %s Importar do outro volume Falha ao abrir este arquivo. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 6b6355f..2eda505 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -37,7 +37,7 @@ Открыть внешним приложением Удалить %s? Удалить %s элементов? - Путь: /%s + Путь: %s Общий размер: %s Импорт из другого тома Невозможно открыть файл. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3cd357b..f46d1ca 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -39,7 +39,7 @@ Open with external app Are you sure you want to delete %s ? Are you sure you want to delete these %s items ? - Location: /%s + Location: %s Total size: %s Import from another volume Failed to open this file.