Skip to content

Commit 7d5cbe3

Browse files
authored
Merge pull request #83 from flogy/read-file-chunks
feat: allow reading chunks from files
2 parents ddd5b76 + 2796a48 commit 7d5cbe3

File tree

7 files changed

+89
-0
lines changed

7 files changed

+89
-0
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,11 @@ type ManagedFetchResult = {
164164
- Read the content of a file.
165165
- Default encoding of returned string is utf8.
166166

167+
`FileSystem.readFileChunk(path: string, offset: number, length: number, encoding?: 'utf8' | 'base64'): Promise<string>`
168+
169+
- Read a chunk of the content of a file, starting from byte at `offset`, reading for `length` bytes.
170+
- Default encoding of returned string is utf8.
171+
167172
```
168173
FileSystem.stat(path: string): Promise<FileStat>
169174

android/src/main/java/com/alpha0010/fs/FileAccessModule.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,30 @@ class FileAccessModule internal constructor(context: ReactApplicationContext) :
400400
}
401401
}
402402

403+
@ReactMethod
404+
override fun readFileChunk(path: String, offset: Double, length: Double, encoding: String, promise: Promise) {
405+
ioScope.launch {
406+
try {
407+
val data = openForReading(path).use { inputStream ->
408+
inputStream.skip(offset.toLong())
409+
val data = ByteArray(length.toInt())
410+
inputStream.read(data)
411+
data
412+
}
413+
414+
val result = if (encoding == "base64") {
415+
Base64.encodeToString(data, Base64.NO_WRAP)
416+
} else {
417+
data.decodeToString()
418+
}
419+
420+
promise.resolve(result)
421+
} catch (e: Throwable) {
422+
promise.reject(e)
423+
}
424+
}
425+
}
426+
403427
@ReactMethod
404428
override fun stat(path: String, promise: Promise) {
405429
ioScope.launch {

android/src/oldarch/FileAccessSpec.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ abstract class FileAccessSpec internal constructor(context: ReactApplicationCont
3030
abstract fun mkdir(path: String, promise: Promise)
3131
abstract fun mv(source: String, target: String, promise: Promise)
3232
abstract fun readFile(path: String, encoding: String, promise: Promise)
33+
abstract fun readFileChunk(path: String, offset: Double, length: Double, encoding: String, promise: Promise)
3334
abstract fun stat(path: String, promise: Promise)
3435
abstract fun statDir(path: String, promise: Promise)
3536
abstract fun unlink(path: String, promise: Promise)

ios/FileAccess.mm

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ - (void)ls:(NSString * _Nonnull)path withResolver:(RCTPromiseResolveBlock _Nonnu
2222
- (void)mkdir:(NSString * _Nonnull)path withResolver:(RCTPromiseResolveBlock _Nonnull)resolve withRejecter:(RCTPromiseRejectBlock _Nonnull)reject;
2323
- (void)mv:(NSString * _Nonnull)source withTarget:(NSString * _Nonnull)target withResolver:(RCTPromiseResolveBlock _Nonnull)resolve withRejecter:(RCTPromiseRejectBlock _Nonnull)reject;
2424
- (void)readFile:(NSString * _Nonnull)path withEncoding:(NSString * _Nonnull)encoding withResolver:(RCTPromiseResolveBlock _Nonnull)resolve withRejecter:(RCTPromiseRejectBlock _Nonnull)reject;
25+
- (void)readFileChunk:(NSString * _Nonnull)path withOffset:(NSNumber * _Nonnull)offset withLength:(NSNumber * _Nonnull)length withEncoding:(NSString * _Nonnull)encoding withResolver:(RCTPromiseResolveBlock _Nonnull)resolve withRejecter:(RCTPromiseRejectBlock _Nonnull)reject;
2526
- (void)stat:(NSString * _Nonnull)path withResolver:(RCTPromiseResolveBlock _Nonnull)resolve withRejecter:(RCTPromiseRejectBlock _Nonnull)reject;
2627
- (void)statDir:(NSString * _Nonnull)path withResolver:(RCTPromiseResolveBlock _Nonnull)resolve withRejecter:(RCTPromiseRejectBlock _Nonnull)reject;
2728
- (void)unlink:(NSString * _Nonnull)path withResolver:(RCTPromiseResolveBlock _Nonnull)resolve withRejecter:(RCTPromiseRejectBlock _Nonnull)reject;
@@ -170,6 +171,19 @@ - (instancetype)init
170171
[impl readFile:path withEncoding:encoding withResolver:resolve withRejecter:reject];
171172
}
172173

174+
RCT_EXPORT_METHOD(readFileChunk:(NSString *)path
175+
offset:(double)offset
176+
length:(double)length
177+
encoding:(NSString *)encoding
178+
resolve:(RCTPromiseResolveBlock)resolve
179+
reject:(RCTPromiseRejectBlock)reject)
180+
{
181+
NSNumber *offsetNumber = @(offset);
182+
NSNumber *lengthNumber = @(length);
183+
[impl readFileChunk:path withOffset:offsetNumber withLength:lengthNumber withEncoding:encoding withResolver:resolve withRejecter:reject];
184+
}
185+
186+
173187
RCT_EXPORT_METHOD(stat:(NSString *)path
174188
resolve:(RCTPromiseResolveBlock)resolve
175189
reject:(RCTPromiseRejectBlock)reject)

ios/FileAccess.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,33 @@ public class FileAccess : NSObject {
272272
}
273273
}
274274

275+
@objc(readFileChunk:withOffset:withLength:withEncoding:withResolver:withRejecter:)
276+
public func readFileChunk(path: String, offset: NSNumber, length: NSNumber, encoding: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
277+
DispatchQueue.global().async {
278+
do {
279+
let fileHandle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path.path()))
280+
defer {
281+
fileHandle.closeFile()
282+
}
283+
284+
fileHandle.seek(toFileOffset: UInt64(truncating: offset))
285+
let binaryData = fileHandle.readData(ofLength: Int(truncating: length))
286+
287+
if encoding == "base64" {
288+
resolve(binaryData.base64EncodedString())
289+
} else {
290+
if let content = String(data: binaryData, encoding: .utf8) {
291+
resolve(content)
292+
} else {
293+
reject("ERR", "Failed to decode content from file '\(path)' with specified encoding.", nil)
294+
}
295+
}
296+
} catch {
297+
reject("ERR", "Failed to read '\(path)'. \(error.localizedDescription)", error)
298+
}
299+
}
300+
}
301+
275302
@objc(stat:withResolver:withRejecter:)
276303
public func stat(path: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
277304
DispatchQueue.global().async {

src/NativeFileAccess.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ export interface Spec extends TurboModule {
7171
mkdir(path: string): Promise<string>;
7272
mv(source: string, target: string): Promise<void>;
7373
readFile(path: string, encoding: string): Promise<string>;
74+
readFileChunk(
75+
path: string,
76+
offset: number,
77+
length: number,
78+
encoding: string
79+
): Promise<string>;
7480
stat(path: string): Promise<FileStat>;
7581
statDir(path: string): Promise<FileStat[]>;
7682
unlink(path: string): Promise<void>;

src/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,18 @@ export const FileSystem = {
275275
return FileAccessNative.readFile(path, encoding);
276276
},
277277

278+
/**
279+
* Read a chunk of the content of a file.
280+
*/
281+
readFileChunk(
282+
path: string,
283+
offset: number,
284+
length: number,
285+
encoding: Encoding = 'utf8'
286+
) {
287+
return FileAccessNative.readFileChunk(path, offset, length, encoding);
288+
},
289+
278290
/**
279291
* Read file metadata.
280292
*/

0 commit comments

Comments
 (0)