Skip to content

Commit

Permalink
refactor: file handling
Browse files Browse the repository at this point in the history
* zip files are no longer cached!
* new `FileWrapper` to make development easier
  • Loading branch information
aliernfrog authored Sep 16, 2024
1 parent 2bfd27d commit 0d91efa
Show file tree
Hide file tree
Showing 15 changed files with 358 additions and 448 deletions.
20 changes: 9 additions & 11 deletions app/src/main/aidl/com/aliernfrog/pftool/IFileService.aidl
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,19 @@ interface IFileService {

void copy(String sourcePath, String targetPath) = 2;

void delete(String path) = 3;

boolean exists(String path) = 4;

ServiceFile getFile(String path) = 5;
void createNewFile(String path) = 3;

ServiceFile[] listFiles(String path) = 6;
void delete(String path) = 4;

boolean exists(String path) = 5;

void mkdirs(String path) = 7;
ServiceFile getFile(String path) = 6;

void renameFile(String oldPath, String newPath) = 8;
ServiceFile[] listFiles(String path) = 7;

void unzipMap(String path, String targetPath) = 9;
void mkdirs(String path) = 8;

void zipMap(String path, String targetPath) = 10;
void renameFile(String oldPath, String newPath) = 9;

ParcelFileDescriptor getFd(String path) = 11;
ParcelFileDescriptor getFd(String path) = 10;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.aliernfrog.pftool.data

import androidx.annotation.StringRes
import com.aliernfrog.pftool.R

data class MapActionResult(
val successful: Boolean,
val messageId: Int? = if (successful) null else R.string.warning_error,
@StringRes val message: Int? = if (successful) null else R.string.warning_error,
val newFile: Any? = null
)
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.aliernfrog.pftool.data

import android.os.Parcelable
import com.aliernfrog.pftool.di.getKoinInstance
import com.aliernfrog.pftool.ui.viewmodel.ShizukuViewModel
import com.aliernfrog.pftool.util.getKoinInstance
import com.aliernfrog.pftool.util.staticutil.FileUtil
import kotlinx.parcelize.Parcelize

Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/aliernfrog/pftool/di/Util.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ package com.aliernfrog.pftool.di

import org.koin.mp.KoinPlatformTools

inline fun <reified T : Any> get(): T = KoinPlatformTools.defaultContext().get().get<T>()
inline fun <reified T : Any> getKoinInstance(): T = KoinPlatformTools.defaultContext().get().get<T>()
10 changes: 5 additions & 5 deletions app/src/main/java/com/aliernfrog/pftool/enum/MapAction.kt
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,13 @@ enum class MapAction(
first.runInIOThreadSafe {
val result = first.rename(newName = newName)
if (!result.successful) return@runInIOThreadSafe first.topToastState.showErrorToast(
text = result.messageId ?: R.string.warning_error
text = result.message ?: R.string.warning_error
)
result.newFile?.let {
first.mapsViewModel.chooseMap(it)
}
first.topToastState.showToast(
text = context.getString(result.messageId ?: R.string.maps_renamed)
text = context.getString(result.message ?: R.string.maps_renamed)
.replace("{NAME}", newName),
icon = Icons.Rounded.Edit
)
Expand All @@ -107,13 +107,13 @@ enum class MapAction(
first.runInIOThreadSafe {
val result = first.duplicate(context, newName = newName)
if (!result.successful) return@runInIOThreadSafe first.topToastState.showErrorToast(
text = result.messageId ?: R.string.warning_error
text = result.message ?: R.string.warning_error
)
result.newFile?.let {
first.mapsViewModel.chooseMap(it)
}
first.topToastState.showToast(
text = context.getString(result.messageId ?: R.string.maps_duplicated)
text = context.getString(result.message ?: R.string.maps_duplicated)
.replace("{NAME}", newName),
icon = Icons.Rounded.FileCopy
)
Expand Down Expand Up @@ -249,7 +249,7 @@ private suspend fun runIOAction(
text = context.getString(singleSuccessMessageId).replace("{NAME}", mapName),
icon = successIcon
) else first.topToastState.showErrorToast(
text = context.getString(result.messageId ?: R.string.warning_error)
text = context.getString(result.message ?: R.string.warning_error)
)
result.newFile?.let { first.mapsViewModel.chooseMap(it) }
} else {
Expand Down
227 changes: 227 additions & 0 deletions app/src/main/java/com/aliernfrog/pftool/impl/FileWrapper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
package com.aliernfrog.pftool.impl

import android.content.Context
import android.os.ParcelFileDescriptor
import com.aliernfrog.pftool.data.ServiceFile
import com.aliernfrog.pftool.data.delete
import com.aliernfrog.pftool.data.exists
import com.aliernfrog.pftool.data.listFiles
import com.aliernfrog.pftool.data.nameWithoutExtension
import com.aliernfrog.pftool.data.renameTo
import com.aliernfrog.pftool.di.getKoinInstance
import com.aliernfrog.pftool.ui.viewmodel.ShizukuViewModel
import com.aliernfrog.pftool.util.extension.nameWithoutExtension
import com.aliernfrog.pftool.util.extension.size
import com.lazygeniouz.dfc.file.DocumentFileCompat
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream

@Suppress("IMPLICIT_CAST_TO_ANY")
class FileWrapper(
val file: Any
) {
private val shizukuViewModel = getKoinInstance<ShizukuViewModel>()
private val invalidFileClassException = IllegalArgumentException("FileWrapper: unknown class supplied: ${file.javaClass.name}")

val name: String = when (file) {
is File -> file.name
is DocumentFileCompat -> file.name
is ServiceFile -> file.name
else -> throw invalidFileClassException
}

val path: String = when (file) {
is File -> file.absolutePath
is DocumentFileCompat -> file.uri.toString()
is ServiceFile -> file.path
else -> throw invalidFileClassException
}

val size: Long = when (file) {
is File -> file.size
is DocumentFileCompat -> file.size
is ServiceFile -> file.size
else -> throw invalidFileClassException
}

val lastModified: Long = when (file) {
is File -> file.lastModified()
is DocumentFileCompat -> file.lastModified
is ServiceFile -> file.lastModified
else -> throw invalidFileClassException
}

val isFile: Boolean = when (file) {
is File -> file.isFile
is DocumentFileCompat -> file.isFile()
is ServiceFile -> file.isFile
else -> throw invalidFileClassException
}

val nameWithoutExtension: String = if (isFile) when (file) {
is File -> file.nameWithoutExtension
is DocumentFileCompat -> file.nameWithoutExtension
is ServiceFile -> file.nameWithoutExtension
else -> throw invalidFileClassException
} else name

val parentFile: FileWrapper?
get() = when (file) {
is File -> file.parentFile
is DocumentFileCompat -> file.parentFile
is ServiceFile -> shizukuViewModel.fileService?.getFile(file.parentPath)
else -> throw invalidFileClassException
}?.let { FileWrapper(it) }

private var cachedByteArray: ByteArray? = null
private fun getByteArray(ignoreCache: Boolean = false): ByteArray? {
if (file !is ServiceFile) return null
if (!ignoreCache) cachedByteArray?.let { return it }
val fd = shizukuViewModel.fileService!!.getFd(path)
val input = ParcelFileDescriptor.AutoCloseInputStream(fd)
val output = ByteArrayOutputStream()
input.copyTo(output)
cachedByteArray = output.toByteArray()
output.close()
input.close()
fd.close()
return cachedByteArray
}

val painterModel: Any?
get() = if (isFile) when (file) {
is File, is DocumentFileCompat -> path
is ServiceFile -> getByteArray()
else -> throw invalidFileClassException
} else null

fun listFiles(): List<FileWrapper> {
val list: List<Any> = when (file) {
is File -> file.listFiles()?.toList()
is DocumentFileCompat -> file.listFiles()
is ServiceFile -> file.listFiles()?.toList()
else -> throw invalidFileClassException
} ?: emptyList()
return list.map { FileWrapper(it) }
}

fun findFile(name: String): FileWrapper? {
return when (file) {
is File -> File(file.absolutePath+"/"+name)
is DocumentFileCompat -> file.findFile(name)
is ServiceFile -> shizukuViewModel.fileService!!.getFile(file.path+"/"+name)
else -> throw invalidFileClassException
}?.let { FileWrapper(it) }
}

fun createFile(name: String): FileWrapper? {
val filePath = this.path+"/"+name
return when (file) {
is File -> File(filePath).let {
it.createNewFile()
File(filePath)
}
is DocumentFileCompat -> file.createFile("", name)
is ServiceFile -> {
shizukuViewModel.fileService!!.createNewFile(filePath)
shizukuViewModel.fileService!!.getFile(filePath)
}
else -> throw invalidFileClassException
}?.let { FileWrapper(it) }
}

fun createDirectory(name: String): FileWrapper? {
val filePath = this.path+"/"+name
return when (file) {
is File -> File(filePath).let {
it.mkdirs()
File(filePath)
}
is DocumentFileCompat -> file.createDirectory(name)
is ServiceFile -> {
shizukuViewModel.fileService!!.mkdirs(filePath)
shizukuViewModel.fileService!!.getFile(filePath)
}
else -> throw invalidFileClassException
}?.let { FileWrapper(it) }
}

fun rename(newName: String): FileWrapper? {
return when (file) {
is File -> {
val newPath = (file.parent?.plus("/") ?: "")+newName
file.renameTo(File(newPath))
File(newPath)
}
is DocumentFileCompat -> {
file.renameTo(newName)
file.parentFile?.findFile(newName)
}
is ServiceFile -> {
val newPath = (file.parentPath?.plus("/") ?: "")+newName
file.renameTo(newPath)
shizukuViewModel.fileService!!.getFile(newPath)
}
else -> throw invalidFileClassException
}?.let { FileWrapper(it) }
}

fun exists(): Boolean {
return when (file) {
is File -> file.exists()
is DocumentFileCompat -> file.exists()
is ServiceFile -> file.exists()
else -> throw invalidFileClassException
}
}

fun inputStream(context: Context): InputStream? {
return when (file) {
is File -> file.inputStream()
is DocumentFileCompat -> context.contentResolver.openInputStream(file.uri)
is ServiceFile -> getByteArray(ignoreCache = true)!!.inputStream()
else -> throw invalidFileClassException
}
}

fun outputStream(context: Context): OutputStream? {
return when (file) {
is File -> file.outputStream()
is DocumentFileCompat -> context.contentResolver.openOutputStream(file.uri)
is ServiceFile -> shizukuViewModel.fileService?.getFd(file.path)?.fileDescriptor?.let {
FileOutputStream(it)
}
else -> throw invalidFileClassException
}
}

fun copyFrom(source: FileWrapper, context: Context) {
source.inputStream(context)?.use { inputStream ->
outputStream(context)?.use { outputStream ->
inputStream.copyTo(outputStream)
}
}
}

fun copyTo(target: FileWrapper, context: Context) {
if (this.isFile) target.copyFrom(this, context)
else this.listFiles().forEach { entry ->
if (entry.isFile) {
target.createFile(entry.name)?.let { entry.copyTo(it, context) }
} else {
target.createDirectory(entry.name)?.let { entry.copyTo(it, context) }
}
}
}

fun delete() {
when (file) {
is File -> file.deleteRecursively()
is DocumentFileCompat -> file.delete()
is ServiceFile -> file.delete()
}
}
}
Loading

0 comments on commit 0d91efa

Please sign in to comment.