summaryrefslogtreecommitdiffstats
path: root/platform/android/java
diff options
context:
space:
mode:
authorFredia Huya-Kouadio <fhuya@meta.com>2024-03-07 19:16:25 -0800
committerFredia Huya-Kouadio <fhuyakou@gmail.com>2024-08-26 11:16:37 -0700
commit794ea99240607a7afbc1512582083db82d81ff83 (patch)
treed7401de52eb81dc3170fcf3c4e91722401606461 /platform/android/java
parente63c40e59c6650bdba02c2ceff3390ee9273ac46 (diff)
downloadredot-engine-794ea99240607a7afbc1512582083db82d81ff83.tar.gz
Update the storage access handler logic to support accessing / retrieving contents with the `assets:/` prefix
Diffstat (limited to 'platform/android/java')
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.kt12
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotLib.java5
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/error/Error.kt100
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt21
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/directory/AssetsDirectoryAccess.kt47
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/directory/DirectoryAccessHandler.kt186
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/directory/FilesystemDirectoryAccess.kt6
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/AssetData.kt151
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt220
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessFlags.kt2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt59
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/FileData.kt14
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/FileErrors.kt53
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/MediaStoreData.kt2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt5
15 files changed, 648 insertions, 235 deletions
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
index 111cd48405..1e0027089c 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -83,12 +83,17 @@ import java.util.concurrent.atomic.AtomicReference
*/
class Godot(private val context: Context) {
- private companion object {
+ internal companion object {
private val TAG = Godot::class.java.simpleName
// Supported build flavors
const val EDITOR_FLAVOR = "editor"
const val TEMPLATE_FLAVOR = "template"
+
+ /**
+ * @return true if this is an editor build, false if this is a template build
+ */
+ fun isEditorBuild() = BuildConfig.FLAVOR == EDITOR_FLAVOR
}
private val windowManager: WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
@@ -834,11 +839,6 @@ class Godot(private val context: Context) {
return mClipboard.hasPrimaryClip()
}
- /**
- * @return true if this is an editor build, false if this is a template build
- */
- fun isEditorBuild() = BuildConfig.FLAVOR == EDITOR_FLAVOR
-
fun getClipboard(): String {
val clipData = mClipboard.primaryClip ?: return ""
val text = clipData.getItemAt(0).text ?: return ""
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
index 909daf05c9..295a4a6340 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
@@ -246,4 +246,9 @@ public class GodotLib {
* dispatched from the UI thread.
*/
public static native boolean shouldDispatchInputToRenderThread();
+
+ /**
+ * @return the project resource directory
+ */
+ public static native String getProjectResourceDir();
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/error/Error.kt b/platform/android/java/lib/src/org/godotengine/godot/error/Error.kt
new file mode 100644
index 0000000000..00ef5ee341
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/error/Error.kt
@@ -0,0 +1,100 @@
+/**************************************************************************/
+/* Error.kt */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+package org.godotengine.godot.error
+
+/**
+ * Godot error list.
+ *
+ * This enum MUST match its native counterpart in 'core/error/error_list.h'
+ */
+enum class Error(private val description: String) {
+ OK("OK"), // (0)
+ FAILED("Failed"), ///< Generic fail error
+ ERR_UNAVAILABLE("Unavailable"), ///< What is requested is unsupported/unavailable
+ ERR_UNCONFIGURED("Unconfigured"), ///< The object being used hasn't been properly set up yet
+ ERR_UNAUTHORIZED("Unauthorized"), ///< Missing credentials for requested resource
+ ERR_PARAMETER_RANGE_ERROR("Parameter out of range"), ///< Parameter given out of range (5)
+ ERR_OUT_OF_MEMORY("Out of memory"), ///< Out of memory
+ ERR_FILE_NOT_FOUND("File not found"),
+ ERR_FILE_BAD_DRIVE("File: Bad drive"),
+ ERR_FILE_BAD_PATH("File: Bad path"),
+ ERR_FILE_NO_PERMISSION("File: Permission denied"), // (10)
+ ERR_FILE_ALREADY_IN_USE("File already in use"),
+ ERR_FILE_CANT_OPEN("Can't open file"),
+ ERR_FILE_CANT_WRITE("Can't write file"),
+ ERR_FILE_CANT_READ("Can't read file"),
+ ERR_FILE_UNRECOGNIZED("File unrecognized"), // (15)
+ ERR_FILE_CORRUPT("File corrupt"),
+ ERR_FILE_MISSING_DEPENDENCIES("Missing dependencies for file"),
+ ERR_FILE_EOF("End of file"),
+ ERR_CANT_OPEN("Can't open"), ///< Can't open a resource/socket/file
+ ERR_CANT_CREATE("Can't create"), // (20)
+ ERR_QUERY_FAILED("Query failed"),
+ ERR_ALREADY_IN_USE("Already in use"),
+ ERR_LOCKED("Locked"), ///< resource is locked
+ ERR_TIMEOUT("Timeout"),
+ ERR_CANT_CONNECT("Can't connect"), // (25)
+ ERR_CANT_RESOLVE("Can't resolve"),
+ ERR_CONNECTION_ERROR("Connection error"),
+ ERR_CANT_ACQUIRE_RESOURCE("Can't acquire resource"),
+ ERR_CANT_FORK("Can't fork"),
+ ERR_INVALID_DATA("Invalid data"), ///< Data passed is invalid (30)
+ ERR_INVALID_PARAMETER("Invalid parameter"), ///< Parameter passed is invalid
+ ERR_ALREADY_EXISTS("Already exists"), ///< When adding, item already exists
+ ERR_DOES_NOT_EXIST("Does not exist"), ///< When retrieving/erasing, if item does not exist
+ ERR_DATABASE_CANT_READ("Can't read database"), ///< database is full
+ ERR_DATABASE_CANT_WRITE("Can't write database"), ///< database is full (35)
+ ERR_COMPILATION_FAILED("Compilation failed"),
+ ERR_METHOD_NOT_FOUND("Method not found"),
+ ERR_LINK_FAILED("Link failed"),
+ ERR_SCRIPT_FAILED("Script failed"),
+ ERR_CYCLIC_LINK("Cyclic link detected"), // (40)
+ ERR_INVALID_DECLARATION("Invalid declaration"),
+ ERR_DUPLICATE_SYMBOL("Duplicate symbol"),
+ ERR_PARSE_ERROR("Parse error"),
+ ERR_BUSY("Busy"),
+ ERR_SKIP("Skip"), // (45)
+ ERR_HELP("Help"), ///< user requested help!!
+ ERR_BUG("Bug"), ///< a bug in the software certainly happened, due to a double check failing or unexpected behavior.
+ ERR_PRINTER_ON_FIRE("Printer on fire"); /// the parallel port printer is engulfed in flames
+
+ companion object {
+ internal fun fromNativeValue(nativeValue: Int): Error? {
+ return Error.entries.getOrNull(nativeValue)
+ }
+ }
+
+ internal fun toNativeValue(): Int = this.ordinal
+
+ override fun toString(): String {
+ return description
+ }
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt b/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt
index 8ee3d5f48f..2e5649b563 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt
@@ -34,12 +34,18 @@ import android.content.Context
import android.os.Build
import android.os.Environment
import java.io.File
+import org.godotengine.godot.GodotLib
/**
* Represents the different storage scopes.
*/
internal enum class StorageScope {
/**
+ * Covers the 'assets' directory
+ */
+ ASSETS,
+
+ /**
* Covers internal and external directories accessible to the app without restrictions.
*/
APP,
@@ -56,6 +62,10 @@ internal enum class StorageScope {
class Identifier(context: Context) {
+ companion object {
+ internal const val ASSETS_PREFIX = "assets://"
+ }
+
private val internalAppDir: String? = context.filesDir.canonicalPath
private val internalCacheDir: String? = context.cacheDir.canonicalPath
private val externalAppDir: String? = context.getExternalFilesDir(null)?.canonicalPath
@@ -71,9 +81,16 @@ internal enum class StorageScope {
return UNKNOWN
}
- val pathFile = File(path)
+ if (path.startsWith(ASSETS_PREFIX)) {
+ return ASSETS
+ }
+
+ var pathFile = File(path)
if (!pathFile.isAbsolute) {
- return UNKNOWN
+ pathFile = File(GodotLib.getProjectResourceDir(), path)
+ if (!pathFile.isAbsolute) {
+ return UNKNOWN
+ }
}
// If we have 'All Files Access' permission, we can access all directories without
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/directory/AssetsDirectoryAccess.kt b/platform/android/java/lib/src/org/godotengine/godot/io/directory/AssetsDirectoryAccess.kt
index b9b7ebac6e..523e852518 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/directory/AssetsDirectoryAccess.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/directory/AssetsDirectoryAccess.kt
@@ -33,18 +33,30 @@ package org.godotengine.godot.io.directory
import android.content.Context
import android.util.Log
import android.util.SparseArray
+import org.godotengine.godot.io.StorageScope
import org.godotengine.godot.io.directory.DirectoryAccessHandler.Companion.INVALID_DIR_ID
import org.godotengine.godot.io.directory.DirectoryAccessHandler.Companion.STARTING_DIR_ID
+import org.godotengine.godot.io.file.AssetData
import java.io.File
import java.io.IOException
/**
* Handles directories access within the Android assets directory.
*/
-internal class AssetsDirectoryAccess(context: Context) : DirectoryAccessHandler.DirectoryAccess {
+internal class AssetsDirectoryAccess(private val context: Context) : DirectoryAccessHandler.DirectoryAccess {
companion object {
private val TAG = AssetsDirectoryAccess::class.java.simpleName
+
+ internal fun getAssetsPath(originalPath: String): String {
+ if (originalPath.startsWith(File.separator)) {
+ return originalPath.substring(File.separator.length)
+ }
+ if (originalPath.startsWith(StorageScope.Identifier.ASSETS_PREFIX)) {
+ return originalPath.substring(StorageScope.Identifier.ASSETS_PREFIX.length)
+ }
+ return originalPath
+ }
}
private data class AssetDir(val path: String, val files: Array<String>, var current: Int = 0)
@@ -54,13 +66,6 @@ internal class AssetsDirectoryAccess(context: Context) : DirectoryAccessHandler.
private var lastDirId = STARTING_DIR_ID
private val dirs = SparseArray<AssetDir>()
- private fun getAssetsPath(originalPath: String): String {
- if (originalPath.startsWith(File.separatorChar)) {
- return originalPath.substring(1)
- }
- return originalPath
- }
-
override fun hasDirId(dirId: Int) = dirs.indexOfKey(dirId) >= 0
override fun dirOpen(path: String): Int {
@@ -68,8 +73,8 @@ internal class AssetsDirectoryAccess(context: Context) : DirectoryAccessHandler.
try {
val files = assetManager.list(assetsPath) ?: return INVALID_DIR_ID
// Empty directories don't get added to the 'assets' directory, so
- // if ad.files.length > 0 ==> path is directory
- // if ad.files.length == 0 ==> path is file
+ // if files.length > 0 ==> path is directory
+ // if files.length == 0 ==> path is file
if (files.isEmpty()) {
return INVALID_DIR_ID
}
@@ -89,8 +94,8 @@ internal class AssetsDirectoryAccess(context: Context) : DirectoryAccessHandler.
try {
val files = assetManager.list(assetsPath) ?: return false
// Empty directories don't get added to the 'assets' directory, so
- // if ad.files.length > 0 ==> path is directory
- // if ad.files.length == 0 ==> path is file
+ // if files.length > 0 ==> path is directory
+ // if files.length == 0 ==> path is file
return files.isNotEmpty()
} catch (e: IOException) {
Log.e(TAG, "Exception on dirExists", e)
@@ -98,19 +103,7 @@ internal class AssetsDirectoryAccess(context: Context) : DirectoryAccessHandler.
}
}
- override fun fileExists(path: String): Boolean {
- val assetsPath = getAssetsPath(path)
- try {
- val files = assetManager.list(assetsPath) ?: return false
- // Empty directories don't get added to the 'assets' directory, so
- // if ad.files.length > 0 ==> path is directory
- // if ad.files.length == 0 ==> path is file
- return files.isEmpty()
- } catch (e: IOException) {
- Log.e(TAG, "Exception on fileExists", e)
- return false
- }
- }
+ override fun fileExists(path: String) = AssetData.fileExists(context, path)
override fun dirIsDir(dirId: Int): Boolean {
val ad: AssetDir = dirs[dirId]
@@ -171,7 +164,7 @@ internal class AssetsDirectoryAccess(context: Context) : DirectoryAccessHandler.
override fun getSpaceLeft() = 0L
- override fun rename(from: String, to: String) = false
+ override fun rename(from: String, to: String) = AssetData.rename(from, to)
- override fun remove(filename: String) = false
+ override fun remove(filename: String) = AssetData.delete(filename)
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/directory/DirectoryAccessHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/io/directory/DirectoryAccessHandler.kt
index dd6d5180c5..9f3461200b 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/directory/DirectoryAccessHandler.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/directory/DirectoryAccessHandler.kt
@@ -32,7 +32,8 @@ package org.godotengine.godot.io.directory
import android.content.Context
import android.util.Log
-import org.godotengine.godot.io.directory.DirectoryAccessHandler.AccessType.ACCESS_FILESYSTEM
+import org.godotengine.godot.Godot
+import org.godotengine.godot.io.StorageScope
import org.godotengine.godot.io.directory.DirectoryAccessHandler.AccessType.ACCESS_RESOURCES
/**
@@ -45,18 +46,82 @@ class DirectoryAccessHandler(context: Context) {
internal const val INVALID_DIR_ID = -1
internal const val STARTING_DIR_ID = 1
-
- private fun getAccessTypeFromNative(accessType: Int): AccessType? {
- return when (accessType) {
- ACCESS_RESOURCES.nativeValue -> ACCESS_RESOURCES
- ACCESS_FILESYSTEM.nativeValue -> ACCESS_FILESYSTEM
- else -> null
- }
- }
}
private enum class AccessType(val nativeValue: Int) {
- ACCESS_RESOURCES(0), ACCESS_FILESYSTEM(2)
+ ACCESS_RESOURCES(0),
+
+ /**
+ * Maps to [ACCESS_FILESYSTEM]
+ */
+ ACCESS_USERDATA(1),
+ ACCESS_FILESYSTEM(2);
+
+ fun generateDirAccessId(dirId: Int) = (dirId * DIR_ACCESS_ID_MULTIPLIER) + nativeValue
+
+ companion object {
+ const val DIR_ACCESS_ID_MULTIPLIER = 10
+
+ fun fromDirAccessId(dirAccessId: Int): Pair<AccessType?, Int> {
+ val nativeValue = dirAccessId % DIR_ACCESS_ID_MULTIPLIER
+ val dirId = dirAccessId / DIR_ACCESS_ID_MULTIPLIER
+ return Pair(fromNative(nativeValue), dirId)
+ }
+
+ private fun fromNative(nativeAccessType: Int): AccessType? {
+ for (accessType in entries) {
+ if (accessType.nativeValue == nativeAccessType) {
+ return accessType
+ }
+ }
+ return null
+ }
+
+ fun fromNative(nativeAccessType: Int, storageScope: StorageScope? = null): AccessType? {
+ val accessType = fromNative(nativeAccessType)
+ if (accessType == null) {
+ Log.w(TAG, "Unsupported access type $nativeAccessType")
+ return null
+ }
+
+ // 'Resources' access type takes precedence as it is simple to handle:
+ // if we receive a 'Resources' access type and this is a template build,
+ // we provide a 'Resources' directory handler.
+ // If this is an editor build, 'Resources' refers to the opened project resources
+ // and so we provide a 'Filesystem' directory handler.
+ if (accessType == ACCESS_RESOURCES) {
+ return if (Godot.isEditorBuild()) {
+ ACCESS_FILESYSTEM
+ } else {
+ ACCESS_RESOURCES
+ }
+ } else {
+ // We've received a 'Filesystem' or 'Userdata' access type. On Android, this
+ // may refer to:
+ // - assets directory (path has 'assets:/' prefix)
+ // - app directories
+ // - device shared directories
+ // As such we check the storage scope (if available) to figure what type of
+ // directory handler to provide
+ if (storageScope != null) {
+ val accessTypeFromStorageScope = when (storageScope) {
+ StorageScope.ASSETS -> ACCESS_RESOURCES
+ StorageScope.APP, StorageScope.SHARED -> ACCESS_FILESYSTEM
+ StorageScope.UNKNOWN -> null
+ }
+
+ if (accessTypeFromStorageScope != null) {
+ return accessTypeFromStorageScope
+ }
+ }
+ // If we're not able to infer the type of directory handler from the storage
+ // scope, we fall-back to the 'Filesystem' directory handler as it's the default
+ // for the 'Filesystem' access type.
+ // Note that ACCESS_USERDATA also maps to ACCESS_FILESYSTEM
+ return ACCESS_FILESYSTEM
+ }
+ }
+ }
}
internal interface DirectoryAccess {
@@ -76,8 +141,10 @@ class DirectoryAccessHandler(context: Context) {
fun remove(filename: String): Boolean
}
+ private val storageScopeIdentifier = StorageScope.Identifier(context)
+
private val assetsDirAccess = AssetsDirectoryAccess(context)
- private val fileSystemDirAccess = FilesystemDirectoryAccess(context)
+ private val fileSystemDirAccess = FilesystemDirectoryAccess(context, storageScopeIdentifier)
fun assetsFileExists(assetsPath: String) = assetsDirAccess.fileExists(assetsPath)
fun filesystemFileExists(path: String) = fileSystemDirAccess.fileExists(path)
@@ -85,24 +152,32 @@ class DirectoryAccessHandler(context: Context) {
private fun hasDirId(accessType: AccessType, dirId: Int): Boolean {
return when (accessType) {
ACCESS_RESOURCES -> assetsDirAccess.hasDirId(dirId)
- ACCESS_FILESYSTEM -> fileSystemDirAccess.hasDirId(dirId)
+ else -> fileSystemDirAccess.hasDirId(dirId)
}
}
fun dirOpen(nativeAccessType: Int, path: String?): Int {
- val accessType = getAccessTypeFromNative(nativeAccessType)
- if (path == null || accessType == null) {
+ if (path == null) {
return INVALID_DIR_ID
}
- return when (accessType) {
+ val storageScope = storageScopeIdentifier.identifyStorageScope(path)
+ val accessType = AccessType.fromNative(nativeAccessType, storageScope) ?: return INVALID_DIR_ID
+
+ val dirId = when (accessType) {
ACCESS_RESOURCES -> assetsDirAccess.dirOpen(path)
- ACCESS_FILESYSTEM -> fileSystemDirAccess.dirOpen(path)
+ else -> fileSystemDirAccess.dirOpen(path)
+ }
+ if (dirId == INVALID_DIR_ID) {
+ return INVALID_DIR_ID
}
+
+ val dirAccessId = accessType.generateDirAccessId(dirId)
+ return dirAccessId
}
- fun dirNext(nativeAccessType: Int, dirId: Int): String {
- val accessType = getAccessTypeFromNative(nativeAccessType)
+ fun dirNext(dirAccessId: Int): String {
+ val (accessType, dirId) = AccessType.fromDirAccessId(dirAccessId)
if (accessType == null || !hasDirId(accessType, dirId)) {
Log.w(TAG, "dirNext: Invalid dir id: $dirId")
return ""
@@ -110,12 +185,12 @@ class DirectoryAccessHandler(context: Context) {
return when (accessType) {
ACCESS_RESOURCES -> assetsDirAccess.dirNext(dirId)
- ACCESS_FILESYSTEM -> fileSystemDirAccess.dirNext(dirId)
+ else -> fileSystemDirAccess.dirNext(dirId)
}
}
- fun dirClose(nativeAccessType: Int, dirId: Int) {
- val accessType = getAccessTypeFromNative(nativeAccessType)
+ fun dirClose(dirAccessId: Int) {
+ val (accessType, dirId) = AccessType.fromDirAccessId(dirAccessId)
if (accessType == null || !hasDirId(accessType, dirId)) {
Log.w(TAG, "dirClose: Invalid dir id: $dirId")
return
@@ -123,12 +198,12 @@ class DirectoryAccessHandler(context: Context) {
when (accessType) {
ACCESS_RESOURCES -> assetsDirAccess.dirClose(dirId)
- ACCESS_FILESYSTEM -> fileSystemDirAccess.dirClose(dirId)
+ else -> fileSystemDirAccess.dirClose(dirId)
}
}
- fun dirIsDir(nativeAccessType: Int, dirId: Int): Boolean {
- val accessType = getAccessTypeFromNative(nativeAccessType)
+ fun dirIsDir(dirAccessId: Int): Boolean {
+ val (accessType, dirId) = AccessType.fromDirAccessId(dirAccessId)
if (accessType == null || !hasDirId(accessType, dirId)) {
Log.w(TAG, "dirIsDir: Invalid dir id: $dirId")
return false
@@ -136,91 +211,106 @@ class DirectoryAccessHandler(context: Context) {
return when (accessType) {
ACCESS_RESOURCES -> assetsDirAccess.dirIsDir(dirId)
- ACCESS_FILESYSTEM -> fileSystemDirAccess.dirIsDir(dirId)
+ else -> fileSystemDirAccess.dirIsDir(dirId)
}
}
- fun isCurrentHidden(nativeAccessType: Int, dirId: Int): Boolean {
- val accessType = getAccessTypeFromNative(nativeAccessType)
+ fun isCurrentHidden(dirAccessId: Int): Boolean {
+ val (accessType, dirId) = AccessType.fromDirAccessId(dirAccessId)
if (accessType == null || !hasDirId(accessType, dirId)) {
return false
}
return when (accessType) {
ACCESS_RESOURCES -> assetsDirAccess.isCurrentHidden(dirId)
- ACCESS_FILESYSTEM -> fileSystemDirAccess.isCurrentHidden(dirId)
+ else -> fileSystemDirAccess.isCurrentHidden(dirId)
}
}
fun dirExists(nativeAccessType: Int, path: String?): Boolean {
- val accessType = getAccessTypeFromNative(nativeAccessType)
- if (path == null || accessType == null) {
+ if (path == null) {
return false
}
+ val storageScope = storageScopeIdentifier.identifyStorageScope(path)
+ val accessType = AccessType.fromNative(nativeAccessType, storageScope) ?: return false
+
return when (accessType) {
ACCESS_RESOURCES -> assetsDirAccess.dirExists(path)
- ACCESS_FILESYSTEM -> fileSystemDirAccess.dirExists(path)
+ else -> fileSystemDirAccess.dirExists(path)
}
}
fun fileExists(nativeAccessType: Int, path: String?): Boolean {
- val accessType = getAccessTypeFromNative(nativeAccessType)
- if (path == null || accessType == null) {
+ if (path == null) {
return false
}
+ val storageScope = storageScopeIdentifier.identifyStorageScope(path)
+ val accessType = AccessType.fromNative(nativeAccessType, storageScope) ?: return false
+
return when (accessType) {
ACCESS_RESOURCES -> assetsDirAccess.fileExists(path)
- ACCESS_FILESYSTEM -> fileSystemDirAccess.fileExists(path)
+ else -> fileSystemDirAccess.fileExists(path)
}
}
fun getDriveCount(nativeAccessType: Int): Int {
- val accessType = getAccessTypeFromNative(nativeAccessType) ?: return 0
+ val accessType = AccessType.fromNative(nativeAccessType) ?: return 0
return when(accessType) {
ACCESS_RESOURCES -> assetsDirAccess.getDriveCount()
- ACCESS_FILESYSTEM -> fileSystemDirAccess.getDriveCount()
+ else -> fileSystemDirAccess.getDriveCount()
}
}
fun getDrive(nativeAccessType: Int, drive: Int): String {
- val accessType = getAccessTypeFromNative(nativeAccessType) ?: return ""
+ val accessType = AccessType.fromNative(nativeAccessType) ?: return ""
return when (accessType) {
ACCESS_RESOURCES -> assetsDirAccess.getDrive(drive)
- ACCESS_FILESYSTEM -> fileSystemDirAccess.getDrive(drive)
+ else -> fileSystemDirAccess.getDrive(drive)
}
}
- fun makeDir(nativeAccessType: Int, dir: String): Boolean {
- val accessType = getAccessTypeFromNative(nativeAccessType) ?: return false
+ fun makeDir(nativeAccessType: Int, dir: String?): Boolean {
+ if (dir == null) {
+ return false
+ }
+
+ val storageScope = storageScopeIdentifier.identifyStorageScope(dir)
+ val accessType = AccessType.fromNative(nativeAccessType, storageScope) ?: return false
+
return when (accessType) {
ACCESS_RESOURCES -> assetsDirAccess.makeDir(dir)
- ACCESS_FILESYSTEM -> fileSystemDirAccess.makeDir(dir)
+ else -> fileSystemDirAccess.makeDir(dir)
}
}
fun getSpaceLeft(nativeAccessType: Int): Long {
- val accessType = getAccessTypeFromNative(nativeAccessType) ?: return 0L
+ val accessType = AccessType.fromNative(nativeAccessType) ?: return 0L
return when (accessType) {
ACCESS_RESOURCES -> assetsDirAccess.getSpaceLeft()
- ACCESS_FILESYSTEM -> fileSystemDirAccess.getSpaceLeft()
+ else -> fileSystemDirAccess.getSpaceLeft()
}
}
fun rename(nativeAccessType: Int, from: String, to: String): Boolean {
- val accessType = getAccessTypeFromNative(nativeAccessType) ?: return false
+ val accessType = AccessType.fromNative(nativeAccessType) ?: return false
return when (accessType) {
ACCESS_RESOURCES -> assetsDirAccess.rename(from, to)
- ACCESS_FILESYSTEM -> fileSystemDirAccess.rename(from, to)
+ else -> fileSystemDirAccess.rename(from, to)
}
}
- fun remove(nativeAccessType: Int, filename: String): Boolean {
- val accessType = getAccessTypeFromNative(nativeAccessType) ?: return false
+ fun remove(nativeAccessType: Int, filename: String?): Boolean {
+ if (filename == null) {
+ return false
+ }
+
+ val storageScope = storageScopeIdentifier.identifyStorageScope(filename)
+ val accessType = AccessType.fromNative(nativeAccessType, storageScope) ?: return false
return when (accessType) {
ACCESS_RESOURCES -> assetsDirAccess.remove(filename)
- ACCESS_FILESYSTEM -> fileSystemDirAccess.remove(filename)
+ else -> fileSystemDirAccess.remove(filename)
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/directory/FilesystemDirectoryAccess.kt b/platform/android/java/lib/src/org/godotengine/godot/io/directory/FilesystemDirectoryAccess.kt
index c8b4f79f30..2830216e12 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/directory/FilesystemDirectoryAccess.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/directory/FilesystemDirectoryAccess.kt
@@ -45,7 +45,7 @@ import java.io.File
/**
* Handles directories access with the internal and external filesystem.
*/
-internal class FilesystemDirectoryAccess(private val context: Context):
+internal class FilesystemDirectoryAccess(private val context: Context, private val storageScopeIdentifier: StorageScope.Identifier):
DirectoryAccessHandler.DirectoryAccess {
companion object {
@@ -54,7 +54,6 @@ internal class FilesystemDirectoryAccess(private val context: Context):
private data class DirData(val dirFile: File, val files: Array<File>, var current: Int = 0)
- private val storageScopeIdentifier = StorageScope.Identifier(context)
private val storageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager
private var lastDirId = STARTING_DIR_ID
private val dirs = SparseArray<DirData>()
@@ -63,7 +62,8 @@ internal class FilesystemDirectoryAccess(private val context: Context):
// Directory access is available for shared storage on Android 11+
// On Android 10, access is also available as long as the `requestLegacyExternalStorage`
// tag is available.
- return storageScopeIdentifier.identifyStorageScope(path) != StorageScope.UNKNOWN
+ val storageScope = storageScopeIdentifier.identifyStorageScope(path)
+ return storageScope != StorageScope.UNKNOWN && storageScope != StorageScope.ASSETS
}
override fun hasDirId(dirId: Int) = dirs.indexOfKey(dirId) >= 0
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/AssetData.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/AssetData.kt
new file mode 100644
index 0000000000..1ab739d90b
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/AssetData.kt
@@ -0,0 +1,151 @@
+/**************************************************************************/
+/* AssetData.kt */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+package org.godotengine.godot.io.file
+
+import android.content.Context
+import android.content.res.AssetManager
+import android.util.Log
+import org.godotengine.godot.error.Error
+import org.godotengine.godot.io.directory.AssetsDirectoryAccess
+import java.io.IOException
+import java.io.InputStream
+import java.lang.UnsupportedOperationException
+import java.nio.ByteBuffer
+import java.nio.channels.Channels
+import java.nio.channels.ReadableByteChannel
+
+/**
+ * Implementation of the [DataAccess] which handles access and interaction with files in the
+ * 'assets' directory
+ */
+internal class AssetData(context: Context, private val filePath: String, accessFlag: FileAccessFlags) : DataAccess() {
+
+ companion object {
+ private val TAG = AssetData::class.java.simpleName
+
+ fun fileExists(context: Context, path: String): Boolean {
+ val assetsPath = AssetsDirectoryAccess.getAssetsPath(path)
+ try {
+ val files = context.assets.list(assetsPath) ?: return false
+ // Empty directories don't get added to the 'assets' directory, so
+ // if files.length > 0 ==> path is directory
+ // if files.length == 0 ==> path is file
+ return files.isEmpty()
+ } catch (e: IOException) {
+ Log.e(TAG, "Exception on fileExists", e)
+ return false
+ }
+ }
+
+ fun fileLastModified(path: String) = 0L
+
+ fun delete(path: String) = false
+
+ fun rename(from: String, to: String) = false
+ }
+
+ private val inputStream: InputStream
+ internal val readChannel: ReadableByteChannel
+
+ private var position = 0L
+ private val length: Long
+
+ init {
+ if (accessFlag == FileAccessFlags.WRITE) {
+ throw UnsupportedOperationException("Writing to the 'assets' directory is not supported")
+ }
+
+ val assetsPath = AssetsDirectoryAccess.getAssetsPath(filePath)
+ inputStream = context.assets.open(assetsPath, AssetManager.ACCESS_BUFFER)
+ readChannel = Channels.newChannel(inputStream)
+
+ length = inputStream.available().toLong()
+ }
+
+ override fun close() {
+ try {
+ inputStream.close()
+ } catch (e: IOException) {
+ Log.w(TAG, "Exception when closing file $filePath.", e)
+ }
+ }
+
+ override fun flush() {
+ Log.w(TAG, "flush() is not supported.")
+ }
+
+ override fun seek(position: Long) {
+ try {
+ inputStream.skip(position)
+
+ this.position = position
+ if (this.position > length) {
+ this.position = length
+ endOfFile = true
+ } else {
+ endOfFile = false
+ }
+
+ } catch(e: IOException) {
+ Log.w(TAG, "Exception when seeking file $filePath.", e)
+ }
+ }
+
+ override fun resize(length: Long): Error {
+ Log.w(TAG, "resize() is not supported.")
+ return Error.ERR_UNAVAILABLE
+ }
+
+ override fun position() = position
+
+ override fun size() = length
+
+ override fun read(buffer: ByteBuffer): Int {
+ return try {
+ val readBytes = readChannel.read(buffer)
+ if (readBytes == -1) {
+ endOfFile = true
+ 0
+ } else {
+ position += readBytes
+ endOfFile = position() >= size()
+ readBytes
+ }
+ } catch (e: IOException) {
+ Log.w(TAG, "Exception while reading from $filePath.", e)
+ 0
+ }
+ }
+
+ override fun write(buffer: ByteBuffer) {
+ Log.w(TAG, "write() is not supported.")
+ }
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt
index 11cf7b3566..73f020f249 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt
@@ -33,12 +33,17 @@ package org.godotengine.godot.io.file
import android.content.Context
import android.os.Build
import android.util.Log
+import org.godotengine.godot.error.Error
import org.godotengine.godot.io.StorageScope
+import java.io.FileNotFoundException
import java.io.IOException
+import java.io.InputStream
import java.nio.ByteBuffer
+import java.nio.channels.Channels
import java.nio.channels.ClosedChannelException
import java.nio.channels.FileChannel
import java.nio.channels.NonWritableChannelException
+import kotlin.jvm.Throws
import kotlin.math.max
/**
@@ -47,11 +52,37 @@ import kotlin.math.max
* Its derived instances provide concrete implementations to handle regular file access, as well
* as file access through the media store API on versions of Android were scoped storage is enabled.
*/
-internal abstract class DataAccess(private val filePath: String) {
+internal abstract class DataAccess {
companion object {
private val TAG = DataAccess::class.java.simpleName
+ @Throws(java.lang.Exception::class, FileNotFoundException::class)
+ fun getInputStream(storageScope: StorageScope, context: Context, filePath: String): InputStream? {
+ return when(storageScope) {
+ StorageScope.ASSETS -> {
+ val assetData = AssetData(context, filePath, FileAccessFlags.READ)
+ Channels.newInputStream(assetData.readChannel)
+ }
+
+ StorageScope.APP -> {
+ val fileData = FileData(filePath, FileAccessFlags.READ)
+ Channels.newInputStream(fileData.fileChannel)
+ }
+ StorageScope.SHARED -> {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ val mediaStoreData = MediaStoreData(context, filePath, FileAccessFlags.READ)
+ Channels.newInputStream(mediaStoreData.fileChannel)
+ } else {
+ null
+ }
+ }
+
+ StorageScope.UNKNOWN -> null
+ }
+ }
+
+ @Throws(java.lang.Exception::class, FileNotFoundException::class)
fun generateDataAccess(
storageScope: StorageScope,
context: Context,
@@ -61,6 +92,8 @@ internal abstract class DataAccess(private val filePath: String) {
return when (storageScope) {
StorageScope.APP -> FileData(filePath, accessFlag)
+ StorageScope.ASSETS -> AssetData(context, filePath, accessFlag)
+
StorageScope.SHARED -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStoreData(context, filePath, accessFlag)
} else {
@@ -74,7 +107,13 @@ internal abstract class DataAccess(private val filePath: String) {
fun fileExists(storageScope: StorageScope, context: Context, path: String): Boolean {
return when(storageScope) {
StorageScope.APP -> FileData.fileExists(path)
- StorageScope.SHARED -> MediaStoreData.fileExists(context, path)
+ StorageScope.ASSETS -> AssetData.fileExists(context, path)
+ StorageScope.SHARED -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ MediaStoreData.fileExists(context, path)
+ } else {
+ false
+ }
+
StorageScope.UNKNOWN -> false
}
}
@@ -82,7 +121,13 @@ internal abstract class DataAccess(private val filePath: String) {
fun fileLastModified(storageScope: StorageScope, context: Context, path: String): Long {
return when(storageScope) {
StorageScope.APP -> FileData.fileLastModified(path)
- StorageScope.SHARED -> MediaStoreData.fileLastModified(context, path)
+ StorageScope.ASSETS -> AssetData.fileLastModified(path)
+ StorageScope.SHARED -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ MediaStoreData.fileLastModified(context, path)
+ } else {
+ 0L
+ }
+
StorageScope.UNKNOWN -> 0L
}
}
@@ -90,7 +135,13 @@ internal abstract class DataAccess(private val filePath: String) {
fun removeFile(storageScope: StorageScope, context: Context, path: String): Boolean {
return when(storageScope) {
StorageScope.APP -> FileData.delete(path)
- StorageScope.SHARED -> MediaStoreData.delete(context, path)
+ StorageScope.ASSETS -> AssetData.delete(path)
+ StorageScope.SHARED -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ MediaStoreData.delete(context, path)
+ } else {
+ false
+ }
+
StorageScope.UNKNOWN -> false
}
}
@@ -98,103 +149,120 @@ internal abstract class DataAccess(private val filePath: String) {
fun renameFile(storageScope: StorageScope, context: Context, from: String, to: String): Boolean {
return when(storageScope) {
StorageScope.APP -> FileData.rename(from, to)
- StorageScope.SHARED -> MediaStoreData.rename(context, from, to)
+ StorageScope.ASSETS -> AssetData.rename(from, to)
+ StorageScope.SHARED -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ MediaStoreData.rename(context, from, to)
+ } else {
+ false
+ }
+
StorageScope.UNKNOWN -> false
}
}
}
- protected abstract val fileChannel: FileChannel
internal var endOfFile = false
+ abstract fun close()
+ abstract fun flush()
+ abstract fun seek(position: Long)
+ abstract fun resize(length: Long): Error
+ abstract fun position(): Long
+ abstract fun size(): Long
+ abstract fun read(buffer: ByteBuffer): Int
+ abstract fun write(buffer: ByteBuffer)
- fun close() {
- try {
- fileChannel.close()
- } catch (e: IOException) {
- Log.w(TAG, "Exception when closing file $filePath.", e)
- }
+ fun seekFromEnd(positionFromEnd: Long) {
+ val positionFromBeginning = max(0, size() - positionFromEnd)
+ seek(positionFromBeginning)
}
- fun flush() {
- try {
- fileChannel.force(false)
- } catch (e: IOException) {
- Log.w(TAG, "Exception when flushing file $filePath.", e)
+ abstract class FileChannelDataAccess(private val filePath: String) : DataAccess() {
+ internal abstract val fileChannel: FileChannel
+
+ override fun close() {
+ try {
+ fileChannel.close()
+ } catch (e: IOException) {
+ Log.w(TAG, "Exception when closing file $filePath.", e)
+ }
}
- }
- fun seek(position: Long) {
- try {
- fileChannel.position(position)
- endOfFile = position >= fileChannel.size()
- } catch (e: Exception) {
- Log.w(TAG, "Exception when seeking file $filePath.", e)
+ override fun flush() {
+ try {
+ fileChannel.force(false)
+ } catch (e: IOException) {
+ Log.w(TAG, "Exception when flushing file $filePath.", e)
+ }
}
- }
- fun seekFromEnd(positionFromEnd: Long) {
- val positionFromBeginning = max(0, size() - positionFromEnd)
- seek(positionFromBeginning)
- }
+ override fun seek(position: Long) {
+ try {
+ fileChannel.position(position)
+ endOfFile = position >= fileChannel.size()
+ } catch (e: Exception) {
+ Log.w(TAG, "Exception when seeking file $filePath.", e)
+ }
+ }
- fun resize(length: Long): Int {
- return try {
- fileChannel.truncate(length)
- FileErrors.OK.nativeValue
- } catch (e: NonWritableChannelException) {
- FileErrors.FILE_CANT_OPEN.nativeValue
- } catch (e: ClosedChannelException) {
- FileErrors.FILE_CANT_OPEN.nativeValue
- } catch (e: IllegalArgumentException) {
- FileErrors.INVALID_PARAMETER.nativeValue
- } catch (e: IOException) {
- FileErrors.FAILED.nativeValue
+ override fun resize(length: Long): Error {
+ return try {
+ fileChannel.truncate(length)
+ Error.OK
+ } catch (e: NonWritableChannelException) {
+ Error.ERR_FILE_CANT_OPEN
+ } catch (e: ClosedChannelException) {
+ Error.ERR_FILE_CANT_OPEN
+ } catch (e: IllegalArgumentException) {
+ Error.ERR_INVALID_PARAMETER
+ } catch (e: IOException) {
+ Error.FAILED
+ }
}
- }
- fun position(): Long {
- return try {
- fileChannel.position()
+ override fun position(): Long {
+ return try {
+ fileChannel.position()
+ } catch (e: IOException) {
+ Log.w(
+ TAG,
+ "Exception when retrieving position for file $filePath.",
+ e
+ )
+ 0L
+ }
+ }
+
+ override fun size() = try {
+ fileChannel.size()
} catch (e: IOException) {
- Log.w(
- TAG,
- "Exception when retrieving position for file $filePath.",
- e
- )
+ Log.w(TAG, "Exception when retrieving size for file $filePath.", e)
0L
}
- }
-
- fun size() = try {
- fileChannel.size()
- } catch (e: IOException) {
- Log.w(TAG, "Exception when retrieving size for file $filePath.", e)
- 0L
- }
- fun read(buffer: ByteBuffer): Int {
- return try {
- val readBytes = fileChannel.read(buffer)
- endOfFile = readBytes == -1 || (fileChannel.position() >= fileChannel.size())
- if (readBytes == -1) {
+ override fun read(buffer: ByteBuffer): Int {
+ return try {
+ val readBytes = fileChannel.read(buffer)
+ endOfFile = readBytes == -1 || (fileChannel.position() >= fileChannel.size())
+ if (readBytes == -1) {
+ 0
+ } else {
+ readBytes
+ }
+ } catch (e: IOException) {
+ Log.w(TAG, "Exception while reading from file $filePath.", e)
0
- } else {
- readBytes
}
- } catch (e: IOException) {
- Log.w(TAG, "Exception while reading from file $filePath.", e)
- 0
}
- }
- fun write(buffer: ByteBuffer) {
- try {
- val writtenBytes = fileChannel.write(buffer)
- if (writtenBytes > 0) {
- endOfFile = false
+ override fun write(buffer: ByteBuffer) {
+ try {
+ val writtenBytes = fileChannel.write(buffer)
+ if (writtenBytes > 0) {
+ endOfFile = false
+ }
+ } catch (e: IOException) {
+ Log.w(TAG, "Exception while writing to file $filePath.", e)
}
- } catch (e: IOException) {
- Log.w(TAG, "Exception while writing to file $filePath.", e)
}
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessFlags.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessFlags.kt
index 38974af753..f81127e90a 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessFlags.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessFlags.kt
@@ -76,7 +76,7 @@ internal enum class FileAccessFlags(val nativeValue: Int) {
companion object {
fun fromNativeModeFlags(modeFlag: Int): FileAccessFlags? {
- for (flag in values()) {
+ for (flag in entries) {
if (flag.nativeValue == modeFlag) {
return flag
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt
index 1d773467e8..5d57052ce6 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt
@@ -33,8 +33,11 @@ package org.godotengine.godot.io.file
import android.content.Context
import android.util.Log
import android.util.SparseArray
+import org.godotengine.godot.error.Error
import org.godotengine.godot.io.StorageScope
import java.io.FileNotFoundException
+import java.io.InputStream
+import java.lang.UnsupportedOperationException
import java.nio.ByteBuffer
/**
@@ -45,8 +48,20 @@ class FileAccessHandler(val context: Context) {
companion object {
private val TAG = FileAccessHandler::class.java.simpleName
- internal const val INVALID_FILE_ID = 0
+ private const val INVALID_FILE_ID = 0
private const val STARTING_FILE_ID = 1
+ private val FILE_OPEN_FAILED = Pair(Error.FAILED, INVALID_FILE_ID)
+
+ internal fun getInputStream(context: Context, storageScopeIdentifier: StorageScope.Identifier, path: String?): InputStream? {
+ val storageScope = storageScopeIdentifier.identifyStorageScope(path)
+ return try {
+ path?.let {
+ DataAccess.getInputStream(storageScope, context, path)
+ }
+ } catch (e: Exception) {
+ null
+ }
+ }
internal fun fileExists(context: Context, storageScopeIdentifier: StorageScope.Identifier, path: String?): Boolean {
val storageScope = storageScopeIdentifier.identifyStorageScope(path)
@@ -98,29 +113,45 @@ class FileAccessHandler(val context: Context) {
private fun hasFileId(fileId: Int) = files.indexOfKey(fileId) >= 0
+ /**
+ * Returns a positive (> 0) file id when the operation succeeds.
+ * Otherwise, returns a negative value of [Error].
+ */
fun fileOpen(path: String?, modeFlags: Int): Int {
- val accessFlag = FileAccessFlags.fromNativeModeFlags(modeFlags) ?: return INVALID_FILE_ID
- return fileOpen(path, accessFlag)
+ val (fileError, fileId) = fileOpen(path, FileAccessFlags.fromNativeModeFlags(modeFlags))
+ return if (fileError == Error.OK) {
+ fileId
+ } else {
+ // Return the negative of the [Error#toNativeValue()] value to differentiate from the
+ // positive file id.
+ -fileError.toNativeValue()
+ }
}
- internal fun fileOpen(path: String?, accessFlag: FileAccessFlags): Int {
+ internal fun fileOpen(path: String?, accessFlag: FileAccessFlags?): Pair<Error, Int> {
+ if (accessFlag == null) {
+ return FILE_OPEN_FAILED
+ }
+
val storageScope = storageScopeIdentifier.identifyStorageScope(path)
if (storageScope == StorageScope.UNKNOWN) {
- return INVALID_FILE_ID
+ return FILE_OPEN_FAILED
}
return try {
path?.let {
- val dataAccess = DataAccess.generateDataAccess(storageScope, context, it, accessFlag) ?: return INVALID_FILE_ID
+ val dataAccess = DataAccess.generateDataAccess(storageScope, context, it, accessFlag) ?: return FILE_OPEN_FAILED
files.put(++lastFileId, dataAccess)
- lastFileId
- } ?: INVALID_FILE_ID
+ Pair(Error.OK, lastFileId)
+ } ?: FILE_OPEN_FAILED
} catch (e: FileNotFoundException) {
- FileErrors.FILE_NOT_FOUND.nativeValue
+ Pair(Error.ERR_FILE_NOT_FOUND, INVALID_FILE_ID)
+ } catch (e: UnsupportedOperationException) {
+ Pair(Error.ERR_UNAVAILABLE, INVALID_FILE_ID)
} catch (e: Exception) {
Log.w(TAG, "Error while opening $path", e)
- INVALID_FILE_ID
+ FILE_OPEN_FAILED
}
}
@@ -172,6 +203,10 @@ class FileAccessHandler(val context: Context) {
files[fileId].flush()
}
+ fun getInputStream(path: String?) = Companion.getInputStream(context, storageScopeIdentifier, path)
+
+ fun renameFile(from: String, to: String) = Companion.renameFile(context, storageScopeIdentifier, from, to)
+
fun fileExists(path: String?) = Companion.fileExists(context, storageScopeIdentifier, path)
fun fileLastModified(filepath: String?): Long {
@@ -191,10 +226,10 @@ class FileAccessHandler(val context: Context) {
fun fileResize(fileId: Int, length: Long): Int {
if (!hasFileId(fileId)) {
- return FileErrors.FAILED.nativeValue
+ return Error.FAILED.toNativeValue()
}
- return files[fileId].resize(length)
+ return files[fileId].resize(length).toNativeValue()
}
fun fileGetPosition(fileId: Int): Long {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileData.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileData.kt
index d0b8a8dffa..873daada3c 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileData.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileData.kt
@@ -38,7 +38,7 @@ import java.nio.channels.FileChannel
/**
* Implementation of [DataAccess] which handles regular (not scoped) file access and interactions.
*/
-internal class FileData(filePath: String, accessFlag: FileAccessFlags) : DataAccess(filePath) {
+internal class FileData(filePath: String, accessFlag: FileAccessFlags) : DataAccess.FileChannelDataAccess(filePath) {
companion object {
private val TAG = FileData::class.java.simpleName
@@ -80,10 +80,16 @@ internal class FileData(filePath: String, accessFlag: FileAccessFlags) : DataAcc
override val fileChannel: FileChannel
init {
- if (accessFlag == FileAccessFlags.WRITE) {
- fileChannel = FileOutputStream(filePath, !accessFlag.shouldTruncate()).channel
+ fileChannel = if (accessFlag == FileAccessFlags.WRITE) {
+ // Create parent directory is necessary
+ val parentDir = File(filePath).parentFile
+ if (parentDir != null && !parentDir.exists()) {
+ parentDir.mkdirs()
+ }
+
+ FileOutputStream(filePath, !accessFlag.shouldTruncate()).channel
} else {
- fileChannel = RandomAccessFile(filePath, accessFlag.getMode()).channel
+ RandomAccessFile(filePath, accessFlag.getMode()).channel
}
if (accessFlag.shouldTruncate()) {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileErrors.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileErrors.kt
deleted file mode 100644
index 2df0195de7..0000000000
--- a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileErrors.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/**************************************************************************/
-/* FileErrors.kt */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-package org.godotengine.godot.io.file
-
-/**
- * Set of errors that may occur when performing data access.
- */
-internal enum class FileErrors(val nativeValue: Int) {
- OK(0),
- FAILED(-1),
- FILE_NOT_FOUND(-2),
- FILE_CANT_OPEN(-3),
- INVALID_PARAMETER(-4);
-
- companion object {
- fun fromNativeError(error: Int): FileErrors? {
- for (fileError in entries) {
- if (fileError.nativeValue == error) {
- return fileError
- }
- }
- return null
- }
- }
-}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/MediaStoreData.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/MediaStoreData.kt
index 146fc04da4..97362e2542 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/file/MediaStoreData.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/MediaStoreData.kt
@@ -52,7 +52,7 @@ import java.nio.channels.FileChannel
*/
@RequiresApi(Build.VERSION_CODES.Q)
internal class MediaStoreData(context: Context, filePath: String, accessFlag: FileAccessFlags) :
- DataAccess(filePath) {
+ DataAccess.FileChannelDataAccess(filePath) {
private data class DataItem(
val id: Long,
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt b/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt
index d39f2309b8..738f27e877 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt
@@ -37,6 +37,7 @@ import android.os.SystemClock
import android.os.Trace
import android.util.Log
import org.godotengine.godot.BuildConfig
+import org.godotengine.godot.error.Error
import org.godotengine.godot.io.file.FileAccessFlags
import org.godotengine.godot.io.file.FileAccessHandler
import org.json.JSONObject
@@ -128,8 +129,8 @@ fun dumpBenchmark(fileAccessHandler: FileAccessHandler? = null, filepath: String
Log.i(TAG, "BENCHMARK:\n$printOut")
if (fileAccessHandler != null && !filepath.isNullOrBlank()) {
- val fileId = fileAccessHandler.fileOpen(filepath, FileAccessFlags.WRITE)
- if (fileId != FileAccessHandler.INVALID_FILE_ID) {
+ val (fileError, fileId) = fileAccessHandler.fileOpen(filepath, FileAccessFlags.WRITE)
+ if (fileError == Error.OK) {
val jsonOutput = JSONObject(benchmarkTracker.toMap()).toString(4)
fileAccessHandler.fileWrite(fileId, ByteBuffer.wrap(jsonOutput.toByteArray()))
fileAccessHandler.fileClose(fileId)