Skip to content

Commit 5272920

Browse files
authored
Merge pull request #25 from PureSwift/feature/file-manager
Add `AndroidFileManager` target
2 parents ddbf089 + a30c16d commit 5272920

10 files changed

Lines changed: 652 additions & 1 deletion

File tree

Package.swift

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ var package = Package(
7878
),
7979
.library(
8080
name: "AndroidHardware", targets: ["AndroidHardware"]
81+
),
82+
.library(
83+
name: "AndroidFileManager", targets: ["AndroidFileManager"]
8184
)
8285
],
8386
dependencies: [
@@ -149,7 +152,8 @@ var package = Package(
149152
"AndroidWebKit",
150153
"AndroidLogging",
151154
"AndroidLooper",
152-
"AndroidHardware"
155+
"AndroidHardware",
156+
"AndroidFileManager"
153157
],
154158
swiftSettings: [
155159
.swiftLanguageMode(.v5),
@@ -293,6 +297,8 @@ var package = Package(
293297
"AndroidJava",
294298
"AndroidUtil",
295299
"AndroidOS",
300+
"AndroidNDK",
301+
"AndroidFileManager",
296302
],
297303
exclude: ["swift-java.config"],
298304
swiftSettings: [
@@ -467,6 +473,20 @@ var package = Package(
467473
linkerSettings: [
468474
.linkedLibrary("android", .when(platforms: [.android]))
469475
]
476+
),
477+
.target(
478+
name: "AndroidFileManager",
479+
dependencies: [
480+
"AndroidNDK"
481+
],
482+
swiftSettings: [
483+
.swiftLanguageMode(.v6),
484+
ndkVersionDefine,
485+
sdkVersionDefine
486+
],
487+
linkerSettings: [
488+
.linkedLibrary("android", .when(platforms: [.android]))
489+
]
470490
)
471491
],
472492
swiftLanguageModes: [.v5, .v6]
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//
2+
// AssetManagerNDK.swift
3+
// SwiftAndroid
4+
//
5+
// Created by Alsey Coleman Miller on 2/27/26.
6+
//
7+
8+
#if canImport(Android)
9+
import Android
10+
import AndroidNDK
11+
#endif
12+
import SwiftJava
13+
import CSwiftJavaJNI
14+
import AndroidFileManager
15+
16+
// MARK: - NDK Asset Manager
17+
18+
public extension AssetManager {
19+
20+
/// Equivalent NDK type.
21+
typealias NDK = AndroidFileManager.AssetManager
22+
23+
/// Create a temporary NDK object and perform operations on it.
24+
func withNDK<E, Result>(_ body: (borrowing NDK) throws(E) -> Result) throws(E) -> Result where E: Error {
25+
let ndk = NDK.fromJava(javaThis, environment: javaEnvironment)
26+
return try body(ndk)
27+
}
28+
}
29+
30+
internal extension AndroidFileManager.AssetManager {
31+
32+
/**
33+
* Converts an android.content.res.AssetManager object into an AAssetManager* object.
34+
*
35+
* If the asset manager is null, null is returned.
36+
*
37+
* Available since API level 24.
38+
*
39+
* \param env Java environment. Must not be null.
40+
* \param assetManager android.content.res.AssetManager java object.
41+
*
42+
* \return an AAssetManager object representing the Java AssetManager object. If either parameter
43+
* is null, this will return null.
44+
*/
45+
static func fromJava(_ javaObject: jobject, environment: JNIEnvironment) -> AndroidFileManager.AssetManager {
46+
guard let pointer = AAssetManager_fromJava(environment, javaObject) else {
47+
fatalError("Unable to initialize from Java object")
48+
}
49+
return AndroidFileManager.AssetManager(pointer)
50+
}
51+
}
52+
53+
#if !os(Android)
54+
func AAssetManager_fromJava(_ environment: JNIEnvironment?, _ javaObject: jobject) -> OpaquePointer? { fatalError("stub") }
55+
#endif
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//
2+
// AndroidFileManager.swift
3+
// SwiftAndroid
4+
//
5+
// Created by Alsey Coleman Miller on 2/27/26.
6+
//
7+
8+
#if os(Android)
9+
import Android
10+
import AndroidNDK
11+
#endif
12+
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
//
2+
// Asset.swift
3+
// SwiftAndroid
4+
//
5+
// Created by Alsey Coleman Miller on 2/27/26.
6+
//
7+
8+
#if os(Android)
9+
import Android
10+
import AndroidNDK
11+
#endif
12+
13+
/// A handle to an `AAsset`.
14+
///
15+
/// Asset values own their pointer and close it during deinitialization.
16+
public struct Asset: ~Copyable {
17+
18+
internal let pointer: OpaquePointer
19+
20+
internal init(_ pointer: OpaquePointer) {
21+
self.pointer = pointer
22+
}
23+
24+
deinit {
25+
AAsset_close(pointer)
26+
}
27+
}
28+
29+
// MARK: - Properties
30+
31+
public extension Asset {
32+
33+
/// Total uncompressed length of this asset in bytes.
34+
var length: Int64 {
35+
AAsset_getLength64(pointer)
36+
}
37+
38+
/// Remaining unread bytes in this asset.
39+
var remainingLength: Int64 {
40+
AAsset_getRemainingLength64(pointer)
41+
}
42+
43+
/// Whether the asset is backed by a memory allocation.
44+
var isAllocated: Bool {
45+
AAsset_isAllocated(pointer) != 0
46+
}
47+
}
48+
49+
// MARK: - Methods
50+
51+
public extension Asset {
52+
53+
enum SeekOrigin: Int32, Sendable {
54+
case start = 0
55+
case current = 1
56+
case end = 2
57+
}
58+
59+
/// Reads up to `maxCount` bytes from the current cursor position.
60+
func read(maxCount: Int = 4096) throws(AndroidFileManagerError) -> [UInt8] {
61+
guard maxCount > 0 else {
62+
return []
63+
}
64+
var bytes = [UInt8](repeating: 0, count: maxCount)
65+
let count = AAsset_read(pointer, &bytes, maxCount)
66+
guard count >= 0 else {
67+
throw .readAsset(count)
68+
}
69+
return Array(bytes.prefix(Int(count)))
70+
}
71+
72+
/// Reads and returns all remaining bytes.
73+
func readAll(chunkSize: Int = 4096) throws(AndroidFileManagerError) -> [UInt8] {
74+
guard chunkSize > 0 else {
75+
return []
76+
}
77+
var output = [UInt8]()
78+
output.reserveCapacity(Int(max(remainingLength, 0)))
79+
while true {
80+
let chunk = try read(maxCount: chunkSize)
81+
if chunk.isEmpty {
82+
break
83+
}
84+
output.append(contentsOf: chunk)
85+
}
86+
return output
87+
}
88+
89+
/// Seeks the asset cursor and returns the new absolute position.
90+
///
91+
/// - Parameters:
92+
/// - offset: Signed offset.
93+
/// - whence: `SEEK_SET`, `SEEK_CUR`, or `SEEK_END`.
94+
func seek(offset: Int64, whence: SeekOrigin = .start) throws(AndroidFileManagerError) -> Int64 {
95+
let result = AAsset_seek64(pointer, offset, whence.rawValue)
96+
guard result >= 0 else {
97+
throw .seekAsset(result)
98+
}
99+
return result
100+
}
101+
102+
/// Returns a file descriptor and byte range when available.
103+
func openFileDescriptor() -> (fd: Int32, start: Int64, length: Int64)? {
104+
var start: Int64 = 0
105+
var length: Int64 = 0
106+
let fd = AAsset_openFileDescriptor64(pointer, &start, &length)
107+
guard fd >= 0 else {
108+
return nil
109+
}
110+
return (fd, start, length)
111+
}
112+
113+
/// Returns an in-memory buffer, if this asset exposes one.
114+
func withUnsafeBufferPointer<T>(
115+
_ body: (UnsafeRawBufferPointer) throws -> T
116+
) rethrows -> T? {
117+
guard let baseAddress = AAsset_getBuffer(pointer) else {
118+
return nil
119+
}
120+
let count = Int(max(length, 0))
121+
let buffer = UnsafeRawBufferPointer(start: baseAddress, count: count)
122+
return try body(buffer)
123+
}
124+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//
2+
// AssetManager.swift
3+
// SwiftAndroid
4+
//
5+
// Created by Alsey Coleman Miller on 2/27/26.
6+
//
7+
8+
#if os(Android)
9+
import Android
10+
import AndroidNDK
11+
#endif
12+
13+
/// Wrapper around Android `AAssetManager`.
14+
public struct AssetManager: @unchecked Sendable {
15+
16+
internal let pointer: OpaquePointer
17+
18+
/// Creates a manager from an existing native pointer.
19+
public init(_ pointer: OpaquePointer) {
20+
self.pointer = pointer
21+
}
22+
}
23+
24+
// MARK: - Methods
25+
26+
public extension AssetManager {
27+
28+
/// Opens an asset by path.
29+
///
30+
/// - Parameters:
31+
/// - path: Relative path under the APK `assets/` directory.
32+
/// - mode: Access hint for Android's asset backend.
33+
func open(_ path: String, mode: AssetMode = .streaming) throws(AndroidFileManagerError) -> Asset {
34+
guard let pointer = path.withCString({
35+
AAssetManager_open(pointer, $0, mode.rawValue)
36+
}) else {
37+
throw .openAsset(path)
38+
}
39+
return Asset(pointer)
40+
}
41+
}
42+
43+
// MARK: - Supporting Types
44+
45+
public extension AssetManager {
46+
47+
/// `AAssetManager_open` mode flags.
48+
enum AssetMode: Int32, Sendable {
49+
case unknown = 0
50+
case random = 1
51+
case streaming = 2
52+
case buffer = 3
53+
}
54+
}
55+

0 commit comments

Comments
 (0)