Skip to content

Commit

Permalink
RootFilesystem
Browse files Browse the repository at this point in the history
  • Loading branch information
massivemadness committed Oct 4, 2022
1 parent 02861f8 commit 8ba5662
Show file tree
Hide file tree
Showing 12 changed files with 329 additions and 35 deletions.
4 changes: 1 addition & 3 deletions feature-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,10 @@ dependencies {
implementation library.hilt
kapt library.hilt_compiler

// Other
implementation library.superuser

// Modules
implementation project(':filesystems:filesystem-base')
implementation project(':filesystems:filesystem-local')
implementation project(':filesystems:filesystem-root')
implementation project(':filesystems:filesystem-ftp')
implementation project(':filesystems:filesystem-ftps')
implementation project(':filesystems:filesystem-ftpes')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package com.blacksquircle.ui.core.data.factory

import android.content.Context
import android.os.Environment
import com.blacksquircle.ui.core.data.converter.ServerConverter
import com.blacksquircle.ui.core.data.storage.database.AppDatabase
Expand All @@ -25,33 +24,25 @@ import com.blacksquircle.ui.filesystem.ftp.FTPFilesystem
import com.blacksquircle.ui.filesystem.ftpes.FTPESFilesystem
import com.blacksquircle.ui.filesystem.ftps.FTPSFilesystem
import com.blacksquircle.ui.filesystem.local.LocalFilesystem
import com.blacksquircle.ui.filesystem.root.RootFilesystem
import com.blacksquircle.ui.filesystem.sftp.SFTPFilesystem
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.io.SuFile
import java.io.File

class FilesystemFactory(
private val database: AppDatabase,
private val context: Context,
private val cacheDir: File,
) {

init {
Shell.setDefaultBuilder(
Shell.Builder.create()
.setFlags(Shell.FLAG_MOUNT_MASTER or Shell.FLAG_REDIRECT_STDERR)
.setContext(context)
)
}

suspend fun create(uuid: String?): Filesystem {
val filesystemUuid = uuid ?: LocalFilesystem.LOCAL_UUID
val persistent = database.serverDao().load(filesystemUuid)
return when (filesystemUuid) {
LocalFilesystem.LOCAL_UUID -> LocalFilesystem(defaultLocation())
suspend fun create(uuid: String): Filesystem {
val persistent = database.serverDao().load(uuid)
return when (uuid) {
LocalFilesystem.LOCAL_UUID -> LocalFilesystem(Environment.getExternalStorageDirectory())
RootFilesystem.ROOT_UUID -> RootFilesystem()
persistent?.uuid -> when (persistent.scheme) {
FTPFilesystem.FTP_SCHEME -> FTPFilesystem(ServerConverter.toModel(persistent), cacheLocation())
FTPSFilesystem.FTPS_SCHEME -> FTPSFilesystem(ServerConverter.toModel(persistent), cacheLocation())
FTPESFilesystem.FTPES_SCHEME -> FTPESFilesystem(ServerConverter.toModel(persistent), cacheLocation())
SFTPFilesystem.SFTP_SCHEME -> SFTPFilesystem(ServerConverter.toModel(persistent), cacheLocation())
FTPFilesystem.FTP_SCHEME -> FTPFilesystem(ServerConverter.toModel(persistent), cacheDir)
FTPSFilesystem.FTPS_SCHEME -> FTPSFilesystem(ServerConverter.toModel(persistent), cacheDir)
FTPESFilesystem.FTPES_SCHEME -> FTPESFilesystem(ServerConverter.toModel(persistent), cacheDir)
SFTPFilesystem.SFTP_SCHEME -> SFTPFilesystem(ServerConverter.toModel(persistent), cacheDir)
else -> throw IllegalArgumentException("Unsupported file scheme")
}
else -> throw IllegalArgumentException("Can't find filesystem")
Expand All @@ -60,28 +51,24 @@ class FilesystemFactory(

suspend fun findForPosition(position: Int): Filesystem {
return when (position) {
LOCAL -> LocalFilesystem(defaultLocation()) // Local Storage
ROOT -> LocalFilesystem(rootLocation()) // Root Directory
LOCAL -> LocalFilesystem(Environment.getExternalStorageDirectory()) // Local Storage
ROOT -> RootFilesystem() // Root Directory
else -> { // Server List
val serverModel = database.serverDao().loadAll()
.map(ServerConverter::toModel)
.getOrNull(position - 2) // 0 = local, 1 = root, 2..3.. - servers
?: throw IllegalArgumentException("Can't find filesystem")
when (serverModel.scheme) {
FTPFilesystem.FTP_SCHEME -> FTPFilesystem(serverModel, cacheLocation())
FTPSFilesystem.FTPS_SCHEME -> FTPSFilesystem(serverModel, cacheLocation())
FTPESFilesystem.FTPES_SCHEME -> FTPESFilesystem(serverModel, cacheLocation())
SFTPFilesystem.SFTP_SCHEME -> SFTPFilesystem(serverModel, cacheLocation())
FTPFilesystem.FTP_SCHEME -> FTPFilesystem(serverModel, cacheDir)
FTPSFilesystem.FTPS_SCHEME -> FTPSFilesystem(serverModel, cacheDir)
FTPESFilesystem.FTPES_SCHEME -> FTPESFilesystem(serverModel, cacheDir)
SFTPFilesystem.SFTP_SCHEME -> SFTPFilesystem(serverModel, cacheDir)
else -> throw IllegalArgumentException("Unsupported file scheme")
}
}
}
}

private fun defaultLocation() = Environment.getExternalStorageDirectory()
private fun rootLocation() = SuFile("/")
private fun cacheLocation() = context.cacheDir

companion object {
const val LOCAL = 0
const val ROOT = 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ object CoreModule {
@ApplicationContext context: Context,
appDatabase: AppDatabase,
): FilesystemFactory {
return FilesystemFactory(appDatabase, context)
return FilesystemFactory(appDatabase, context.cacheDir)
}

@Provides
Expand Down
6 changes: 6 additions & 0 deletions feature-settings/src/main/res/raw/changelog.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
-->

<!DOCTYPE html>
<u><b>v2022.2.1, 5 Oct. 2022</b></u><br>
<b>Fixed:</b> Empty file list in Root Directory<br>
<b>Fixed:</b> Crash when switching between servers (<a href="https://github.com/massivemadness/Squircle-CE/commit/861dd29bf121cf4760900c92d87b8d2aaa6420f0">861dd29</a>)<br>
<b>Fixed:</b> Local directories were displayed while using FTP (<a href="https://github.com/massivemadness/Squircle-CE/commit/b64917d633ce1c61564c8dca81fac0f3be3f93a7#diff-6b725303e8fa413c57dd281c6d9b2f4af4e13a72f7e703a6912fe6c4aa5a6c71R225">b64917d</a>)<br>
• Improved interaction with WindowInsets API (<a href="https://github.com/massivemadness/Squircle-CE/commit/f8fecedbf98552e15bd0a573334cc52ca6e20708">f8feced</a>)<br>
<br>
<u><b>v2022.2.0, 1 Oct. 2022</b></u><br>
<b>Added:</b> Support for Cloud Filesystems (FTP, FTPS, SFTP) (<a href="https://github.com/massivemadness/Squircle-CE/commit/d3f08c39e8b6e2c3cd359368eb998d41ee9b6796">d3f08c3</a>)<br>
<b>Added:</b> Support for Root Directory (<a href="https://github.com/massivemadness/Squircle-CE/commit/9948091dfb59f0585f6d961a689f758846a331c9">9948091</a>)<br>
Expand Down
1 change: 1 addition & 0 deletions filesystems/filesystem-root/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
60 changes: 60 additions & 0 deletions filesystems/filesystem-root/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2022 Squircle CE contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

plugins {
id 'com.android.library'
id 'kotlin-android'
}

android {
compileSdkVersion versions.compileSdk
buildToolsVersion versions.buildTools

namespace 'com.blacksquircle.ui.filesystem.root'

defaultConfig {
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk

consumerProguardFiles 'consumer-rules.pro'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '11'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
}

dependencies {

// Core
implementation library.kotlin

// Coroutines
implementation library.coroutines_core

// Other
implementation library.chardet
implementation library.superuser

// Modules
api project(':filesystems:filesystem-base')
}
Empty file.
Empty file.
18 changes: 18 additions & 0 deletions filesystems/filesystem-root/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2022 Squircle CE contributors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->

<manifest />
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/*
* Copyright 2022 Squircle CE contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.blacksquircle.ui.filesystem.root

import com.blacksquircle.ui.filesystem.base.Filesystem
import com.blacksquircle.ui.filesystem.base.exception.DirectoryExpectedException
import com.blacksquircle.ui.filesystem.base.exception.FileAlreadyExistsException
import com.blacksquircle.ui.filesystem.base.exception.FileNotFoundException
import com.blacksquircle.ui.filesystem.base.model.FileModel
import com.blacksquircle.ui.filesystem.base.model.FileParams
import com.blacksquircle.ui.filesystem.base.model.FileTree
import com.blacksquircle.ui.filesystem.base.model.Permission
import com.blacksquircle.ui.filesystem.base.utils.plusFlag
import com.blacksquircle.ui.filesystem.root.utils.requestRootAccess
import com.ibm.icu.text.CharsetDetector
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.io.SuFile
import kotlinx.coroutines.flow.Flow
import java.io.BufferedReader

class RootFilesystem : Filesystem {

init {
requestRootAccess()
}

override fun defaultLocation(): FileModel {
val defaultLocation = SuFile("/")
val fileModel = toFileModel(defaultLocation)
if (!defaultLocation.isDirectory) {
throw DirectoryExpectedException()
}
return fileModel
}

override fun provideDirectory(parent: FileModel): FileTree {
val file = toFileObject(parent)
if (!file.isDirectory) {
throw DirectoryExpectedException()
}
return FileTree(
parent = parent,
children = file.listFiles().orEmpty()
.map(::toFileModel).toList()
)
}

override fun exists(fileModel: FileModel): Boolean {
return SuFile(fileModel.path).exists()
}

override fun createFile(fileModel: FileModel) {
val file = toFileObject(fileModel)
if (file.exists()) {
throw FileAlreadyExistsException(fileModel.path)
}
if (fileModel.directory) {
file.mkdirs()
} else {
val parentFile = file.parentFile!!
if (!parentFile.exists()) {
parentFile.mkdirs()
}
file.createNewFile()
}
}

override fun renameFile(source: FileModel, dest: FileModel) {
val originalFile = toFileObject(source)
val renamedFile = toFileObject(dest)
if (!originalFile.exists())
throw FileNotFoundException(source.path)
if (renamedFile.exists())
throw FileAlreadyExistsException(renamedFile.absolutePath)
originalFile.renameTo(renamedFile)
}

override fun deleteFile(fileModel: FileModel) {
val file = toFileObject(fileModel)
if (!file.exists()) {
throw FileNotFoundException(fileModel.path)
}
file.deleteRecursive()
}

override fun copyFile(source: FileModel, dest: FileModel) {
throw UnsupportedOperationException()
}

override fun compressFiles(source: List<FileModel>, dest: FileModel): Flow<FileModel> {
throw UnsupportedOperationException()
}

override fun extractFiles(source: FileModel, dest: FileModel): Flow<FileModel> {
throw UnsupportedOperationException()
}

override fun loadFile(fileModel: FileModel, fileParams: FileParams): String {
val file = toFileObject(fileModel)
if (!file.exists()) {
throw FileNotFoundException(fileModel.path)
}
val charset = if (fileParams.chardet) {
try {
val charsetMatch = CharsetDetector()
.setText(file.newInputStream().readBytes())
.detect()
charset(charsetMatch.name)
} catch (e: Exception) {
Charsets.UTF_8
}
} else {
fileParams.charset
}
return file.newInputStream().bufferedReader(charset)
.use(BufferedReader::readText)
}

override fun saveFile(fileModel: FileModel, text: String, fileParams: FileParams) {
val file = toFileObject(fileModel)
if (!file.exists()) {
val parentFile = file.parentFile!!
if (!parentFile.exists()) {
parentFile.mkdirs()
}
file.createNewFile()
}
file.newOutputStream().bufferedWriter(fileParams.charset)
.use { it.write(fileParams.linebreak(text)) }
}

companion object : Filesystem.Mapper<SuFile> {

const val ROOT_UUID = "root"
private const val ROOT_SCHEME = "sufile://"

init {
Shell.enableVerboseLogging = BuildConfig.DEBUG
Shell.setDefaultBuilder(
Shell.Builder.create()
.setFlags(Shell.FLAG_MOUNT_MASTER or Shell.FLAG_REDIRECT_STDERR)
)
}

override fun toFileModel(fileObject: SuFile): FileModel {
return FileModel(
fileUri = ROOT_SCHEME + fileObject.path,
filesystemUuid = ROOT_UUID,
size = fileObject.length(),
lastModified = fileObject.lastModified(),
directory = fileObject.isDirectory,
permission = with(fileObject) {
var permission = Permission.EMPTY
if (fileObject.canRead())
permission = permission plusFlag Permission.OWNER_READ
if (fileObject.canWrite())
permission = permission plusFlag Permission.OWNER_WRITE
if (fileObject.canExecute())
permission = permission plusFlag Permission.OWNER_EXECUTE
permission
}
)
}

override fun toFileObject(fileModel: FileModel): SuFile {
return SuFile(fileModel.path)
}
}
}
Loading

0 comments on commit 8ba5662

Please sign in to comment.