Skip to content

Commit 674d19b

Browse files
authored
feat: initial implementation of FileAccess API (#27)
* init * update * chore * chore: tweaks * Chore: move polyfills to user-land, soon back as entry point * chore * feat: expose obj-c to swift and clean-up picker * clean-up * clean-up * update the readme * Revert
1 parent cde0bb0 commit 674d19b

27 files changed

+335
-134
lines changed

README.md

+10-4
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,21 @@
1515

1616
## Getting started
1717

18+
> [!WARNING]
19+
> This library is still under development. Use at your own risk.
20+
1821
```
1922
npm install react-native-fast-io --save
2023
```
2124

22-
> [!NOTE]
23-
> This package requires React Native 0.76 or higher. You must also install and configure [Nitro Modules](https://github.com/mrousavy/nitro) to use this package.
25+
### Prerequisites
2426

25-
> [!WARNING]
26-
> This library is still under development. Use at your own risk.
27+
- React Native 0.76 or higher
28+
- Nitro Modules
29+
- Polyfills:
30+
- TextDecoder, e.g. `@bacons/text-decoder`
31+
- Streams, e.g. `web-streams-polyfill/polyfill`
32+
- AsyncIterator, e.g. `@azure/core-asynciterator-polyfill`
2733

2834
## Usage
2935

example/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import '@bacons/text-decoder/install'
2+
import 'web-streams-polyfill/polyfill'
23
import '@azure/core-asynciterator-polyfill'
34

45
import { AppRegistry } from 'react-native'

example/ios/Podfile.lock

+1-1
Original file line numberDiff line numberDiff line change
@@ -1764,7 +1764,7 @@ EXTERNAL SOURCES:
17641764
SPEC CHECKSUMS:
17651765
boost: 1dca942403ed9342f98334bf4c3621f011aa7946
17661766
DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385
1767-
FastIO: cde737e146ac2f969fa961990c5edf00938804db
1767+
FastIO: 120a9c95af04ba7671b95aa2b5862f64a59a47bf
17681768
FBLazyVector: aa59bef5c46e93168bffcf3dc37ee1e176de799a
17691769
fmt: 10c6e61f4be25dc963c36bd73fc7b1705fe975be
17701770
glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a

example/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"react": "18.3.1",
1515
"react-native": "0.76.0",
1616
"react-native-fast-io": "0.1.1",
17-
"react-native-nitro-modules": "^0.15.0"
17+
"react-native-nitro-modules": "^0.15.0",
18+
"web-streams-polyfill": "^4.0.0"
1819
},
1920
"devDependencies": {
2021
"@babel/core": "^7.20.0",

example/tests/benchmark.tsx

+10-6
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
TouchableOpacity,
1010
View,
1111
} from 'react-native'
12-
import { fetch, readAsFile, WebSocket as FastWS } from 'react-native-fast-io'
12+
import { fetch, showOpenFilePicker, WebSocket as FastWS } from 'react-native-fast-io'
1313

1414
import {
1515
CHAT_PAYLOAD,
@@ -444,13 +444,17 @@ const styles = StyleSheet.create({
444444
})
445445

446446
// tbd: playground
447-
;(async () => {
448-
const img = readAsFile('image.png')
447+
setTimeout(async () => {
448+
const files = await showOpenFilePicker()
449449

450-
// console.log('file', new Uint8Array(await img.arrayBuffer()))
450+
if (!files[0]) {
451+
console.log('No file')
452+
}
453+
454+
const file = await files[0].getFile()
451455

452456
await fetch('http://localhost:3002/upload', {
453457
method: 'POST',
454-
body: img.stream(),
458+
body: file.stream(),
455459
})
456-
})()
460+
}, 2000)

package-lock.json

+3-11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react-native-fast-io/FastIO.podspec

+5-1
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@ Pod::Spec.new do |s|
1414
s.source = { :git => "https://github.com/grabbou/react-native-fast-io.git", :tag => "#{s.version}" }
1515

1616
s.source_files = [
17-
"ios/**/*.{swift}"
17+
"ios/**/*.{h,m,swift}"
1818
]
1919

20+
s.pod_target_xcconfig = {
21+
'SWIFT_INCLUDE_PATHS' => '$(PODS_TARGET_SRCROOT)/ios'
22+
}
23+
2024
load 'nitrogen/generated/ios/FastIO+autolinking.rb'
2125
add_nitrogen_files(s)
2226

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module FastIOPrivate {
2+
header "../RCTUtilsWrapper.h"
3+
export *
4+
}

packages/react-native-fast-io/ios/HybridFileSystem.swift

+61-7
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,85 @@
66
//
77

88
import Foundation
9+
import NitroModules
10+
import FastIOPrivate
911

10-
class HybridFileSystem : HybridFileSystemSpec {
12+
class HybridFileSystem : NSObject, UIDocumentPickerDelegate, HybridFileSystemSpec {
1113
func createInputStream(path: String) -> any HybridInputStreamSpec {
12-
let fakePath = Bundle.main.url(forResource: "img", withExtension: "jpg")!.path
13-
guard let stream = InputStream(fileAtPath: fakePath) else {
14+
guard let stream = InputStream(fileAtPath: path) else {
1415
fatalError("Failed to create stream from \(path)")
1516
}
1617
return HybridInputStream(stream: stream)
1718
}
1819

1920
func getFileMetadata(path: String) throws -> Metadata {
20-
let fileURL = Bundle.main.url(forResource: "img", withExtension: "jpg")!
21-
let attributes = try FileManager.default.attributesOfItem(atPath: fileURL.path)
22-
21+
let attributes = try FileManager.default.attributesOfItem(atPath: path)
22+
let fileURL = URL(fileURLWithPath: path)
23+
2324
return Metadata.init(
2425
name: fileURL.lastPathComponent,
26+
path: path,
27+
root: "/",
2528
size: attributes[.size] as? Double ?? 0,
26-
lastModified: 0
29+
lastModified: (attributes[.modificationDate] as? Date)?.timeIntervalSince1970 ?? 0 * 1000
2730
)
2831
}
2932

33+
private var filePicker: (promise: Promise<[String]>, vc: UIDocumentPickerViewController)?
34+
func showOpenFilePicker() throws -> Promise<[String]> {
35+
if filePicker != nil {
36+
return Promise.rejected(withError: RuntimeError.error(withMessage: "File picker already open"))
37+
}
38+
39+
let promise = Promise<[String]>()
40+
41+
DispatchQueue.main.async {
42+
let documentPicker = UIDocumentPickerViewController(
43+
forOpeningContentTypes: [.item],
44+
asCopy: true
45+
)
46+
documentPicker.delegate = self
47+
48+
guard let vc = RCTUtilsWrapper.getPresentedViewController() else {
49+
promise.reject(withError: RuntimeError.error(withMessage: "Cannot present file picker"))
50+
return
51+
}
52+
53+
vc.present(documentPicker, animated: true)
54+
55+
self.filePicker = (promise, documentPicker)
56+
}
57+
58+
return promise
59+
}
60+
61+
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
62+
controller.dismiss(animated: true, completion: nil)
63+
64+
filePicker?.promise.resolve(withResult: urls.map { $0.path })
65+
filePicker = nil
66+
}
67+
68+
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
69+
controller.dismiss(animated: true, completion: nil)
70+
71+
filePicker?.promise.resolve(withResult: [])
72+
filePicker = nil
73+
}
74+
3075
var hybridContext = margelo.nitro.HybridContext()
3176

3277
// Return size of the instance to inform JS GC about memory pressure
3378
var memorySize: Int {
3479
return getSizeOf(self)
3580
}
81+
82+
deinit {
83+
if let (promise, picker) = filePicker {
84+
promise.resolve(withResult: [])
85+
DispatchQueue.main.async {
86+
picker.dismiss(animated: false)
87+
}
88+
}
89+
}
3690
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#import <UIKit/UIKit.h>
2+
3+
@interface RCTUtilsWrapper : NSObject
4+
+ (UIViewController *)getPresentedViewController;
5+
@end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//
2+
// RCTUtilsWrapper.m
3+
// FastIO
4+
//
5+
// Created by Mike Grabowski on 10/11/2024.
6+
//
7+
8+
#import "RCTUtilsWrapper.h"
9+
#import <React/RCTUtils.h>
10+
11+
@implementation RCTUtilsWrapper
12+
+ (UIViewController *)getPresentedViewController {
13+
return RCTPresentedViewController();
14+
}
15+
@end

packages/react-native-fast-io/nitrogen/generated/ios/FastIO-Swift-Cxx-Bridge.hpp

+20-11
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,26 @@ namespace margelo::nitro::fastio::bridge::swift {
7474
std::shared_ptr<margelo::nitro::fastio::HybridInputStreamSpec> create_std__shared_ptr_margelo__nitro__fastio__HybridInputStreamSpec_(void* _Nonnull swiftUnsafePointer);
7575
void* _Nonnull get_std__shared_ptr_margelo__nitro__fastio__HybridInputStreamSpec_(std__shared_ptr_margelo__nitro__fastio__HybridInputStreamSpec_ cppType);
7676

77+
// pragma MARK: std::vector<std::string>
78+
/**
79+
* Specialized version of `std::vector<std::string>`.
80+
*/
81+
using std__vector_std__string_ = std::vector<std::string>;
82+
inline std::vector<std::string> create_std__vector_std__string_(size_t size) {
83+
std::vector<std::string> vector;
84+
vector.reserve(size);
85+
return vector;
86+
}
87+
88+
// pragma MARK: PromiseHolder<std::vector<std::string>>
89+
/**
90+
* Specialized version of `PromiseHolder<std::vector<std::string>>`.
91+
*/
92+
using PromiseHolder_std__vector_std__string__ = PromiseHolder<std::vector<std::string>>;
93+
inline PromiseHolder<std::vector<std::string>> create_PromiseHolder_std__vector_std__string__() {
94+
return PromiseHolder<std::vector<std::string>>();
95+
}
96+
7797
// pragma MARK: std::shared_ptr<margelo::nitro::fastio::HybridFileSystemSpec>
7898
/**
7999
* Specialized version of `std::shared_ptr<margelo::nitro::fastio::HybridFileSystemSpec>`.
@@ -207,17 +227,6 @@ namespace margelo::nitro::fastio::bridge::swift {
207227
std::shared_ptr<margelo::nitro::fastio::HybridWebSocketSpec> create_std__shared_ptr_margelo__nitro__fastio__HybridWebSocketSpec_(void* _Nonnull swiftUnsafePointer);
208228
void* _Nonnull get_std__shared_ptr_margelo__nitro__fastio__HybridWebSocketSpec_(std__shared_ptr_margelo__nitro__fastio__HybridWebSocketSpec_ cppType);
209229

210-
// pragma MARK: std::vector<std::string>
211-
/**
212-
* Specialized version of `std::vector<std::string>`.
213-
*/
214-
using std__vector_std__string_ = std::vector<std::string>;
215-
inline std::vector<std::string> create_std__vector_std__string_(size_t size) {
216-
std::vector<std::string> vector;
217-
vector.reserve(size);
218-
return vector;
219-
}
220-
221230
// pragma MARK: std::shared_ptr<margelo::nitro::fastio::HybridWebSocketManagerSpec>
222231
/**
223232
* Specialized version of `std::shared_ptr<margelo::nitro::fastio::HybridWebSocketManagerSpec>`.

packages/react-native-fast-io/nitrogen/generated/ios/c++/HybridFileSystemSpecSwift.hpp

+7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ namespace margelo::nitro::fastio { struct Metadata; }
2121
#include "HybridInputStreamSpec.hpp"
2222
#include <string>
2323
#include "Metadata.hpp"
24+
#include <future>
25+
#include <vector>
26+
#include <NitroModules/PromiseHolder.hpp>
2427

2528
#if __has_include(<NitroModules/HybridContext.hpp>)
2629
#include <NitroModules/HybridContext.hpp>
@@ -73,6 +76,10 @@ namespace margelo::nitro::fastio {
7376
auto __result = _swiftPart.getFileMetadata(path);
7477
return __result;
7578
}
79+
inline std::future<std::vector<std::string>> showOpenFilePicker() override {
80+
auto __result = _swiftPart.showOpenFilePicker();
81+
return __result.getFuture();
82+
}
7683

7784
private:
7885
FastIO::HybridFileSystemSpecCxx _swiftPart;

packages/react-native-fast-io/nitrogen/generated/ios/swift/HybridFileSystemSpec.swift

+1
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,5 @@ public protocol HybridFileSystemSpec: AnyObject, HybridObjectSpec {
3434
// Methods
3535
func createInputStream(path: String) throws -> (any HybridInputStreamSpec)
3636
func getFileMetadata(path: String) throws -> Metadata
37+
func showOpenFilePicker() throws -> Promise<[String]>
3738
}

packages/react-native-fast-io/nitrogen/generated/ios/swift/HybridFileSystemSpecCxx.swift

+23
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,27 @@ public class HybridFileSystemSpecCxx {
123123
fatalError("Swift errors can currently not be propagated to C++! See https://github.com/swiftlang/swift/issues/75290 (Error: \(__message))")
124124
}
125125
}
126+
127+
@inline(__always)
128+
public func showOpenFilePicker() -> bridge.PromiseHolder_std__vector_std__string__ {
129+
do {
130+
let __result = try self.__implementation.showOpenFilePicker()
131+
return { () -> bridge.PromiseHolder_std__vector_std__string__ in
132+
let __promiseHolder = bridge.create_PromiseHolder_std__vector_std__string__()
133+
__result
134+
.then({ __result in __promiseHolder.resolve({ () -> bridge.std__vector_std__string_ in
135+
var __vector = bridge.create_std__vector_std__string_(__result.count)
136+
for __item in __result {
137+
__vector.push_back(std.string(__item))
138+
}
139+
return __vector
140+
}()) })
141+
.catch({ __error in __promiseHolder.reject(std.string(String(describing: __error))) })
142+
return __promiseHolder
143+
}()
144+
} catch {
145+
let __message = "\(error.localizedDescription)"
146+
fatalError("Swift errors can currently not be propagated to C++! See https://github.com/swiftlang/swift/issues/75290 (Error: \(__message))")
147+
}
148+
}
126149
}

packages/react-native-fast-io/nitrogen/generated/ios/swift/Metadata.swift

+24-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ public extension Metadata {
1818
/**
1919
* Create a new instance of `Metadata`.
2020
*/
21-
init(name: String, size: Double, lastModified: Double) {
22-
self.init(std.string(name), size, lastModified)
21+
init(name: String, path: String, root: String, size: Double, lastModified: Double) {
22+
self.init(std.string(name), std.string(path), std.string(root), size, lastModified)
2323
}
2424

2525
var name: String {
@@ -33,6 +33,28 @@ public extension Metadata {
3333
}
3434
}
3535

36+
var path: String {
37+
@inline(__always)
38+
get {
39+
return String(self.__path)
40+
}
41+
@inline(__always)
42+
set {
43+
self.__path = std.string(newValue)
44+
}
45+
}
46+
47+
var root: String {
48+
@inline(__always)
49+
get {
50+
return String(self.__root)
51+
}
52+
@inline(__always)
53+
set {
54+
self.__root = std.string(newValue)
55+
}
56+
}
57+
3658
var size: Double {
3759
@inline(__always)
3860
get {

0 commit comments

Comments
 (0)