Skip to content

Commit 6332025

Browse files
authored
Merge pull request #30 from patrickkabwe/feat/download-file
Feat/download file
1 parent e8c77ff commit 6332025

25 files changed

Lines changed: 431 additions & 285 deletions

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on:
44
push:
55
branches:
66
- main
7+
- next
78
paths-ignore:
89
- 'docs/**'
910

NitroFs.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Pod::Spec.new do |s|
2626
# C++ compiler flags, mainly for folly.
2727
"GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) FOLLY_NO_CONFIG FOLLY_CFG_NO_COROUTINES"
2828
}
29-
29+
3030
load 'nitrogen/generated/ios/NitroFS+autolinking.rb'
3131
add_nitrogen_files(s)
3232

README.md

Lines changed: 194 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
11
# react-native-nitro-fs
22

3+
![ChatGPT Image May 17, 2025, 12_31_35 PM](https://github.com/user-attachments/assets/14fa98eb-fbee-4e00-bc85-eb3a8f0e9832)
4+
5+
A high-performance file system module for React Native that handles file operations and transfers with native speed.
6+
7+
## Features
8+
9+
- 💾 File system operations (read, write, copy, delete)
10+
- 📊 Directory management
11+
- ⬆️ File uploads with progress tracking
12+
- ⬇️ File downloads with progress tracking
13+
- 🔎 File existence and stat checking
14+
- ⚡ Native performance with Swift and Kotlin implementations
15+
- 📲 Cross-platform support (iOS and Android)
16+
317
## Requirements
418

5-
- React Native v0.76.0 or higher
19+
- React Native v0.78.0 or higher
620
- Node 18.0.0 or higher
721

822
> [!IMPORTANT]
@@ -14,10 +28,187 @@
1428
bun add react-native-nitro-fs react-native-nitro-modules
1529
```
1630

17-
## Credits
31+
## Quick Start
1832

19-
Bootstrapped with [create-nitro-module](https://github.com/patrickkabwe/create-nitro-module).
33+
```typescript
34+
import { NitroFS } from 'react-native-nitro-fs'
35+
36+
// Check if a file exists
37+
const exists = await NitroFS.exists('/path/to/file')
38+
39+
// Read a file
40+
const content = await NitroFS.readFile('/path/to/file', 'utf8')
41+
42+
// Write to a file
43+
await NitroFS.writeFile('/path/to/file', 'Hello, World!', 'utf8')
44+
45+
// Download a file
46+
const file = await NitroFS.downloadFile(
47+
'https://example.com/file.txt',
48+
'file.txt',
49+
NitroFS.DOWNLOAD_DIR + '/file.txt',
50+
(downloadedBytes, totalBytes) => {
51+
console.log(`Downloading ${(downloadedBytes / totalBytes) * 100}%`)
52+
}
53+
)
54+
```
55+
56+
## API Reference
57+
58+
### Constants
59+
60+
The module provides several directory constants:
61+
62+
- `BUNDLE_DIR`: Directory for storing bundle files
63+
- `DOCUMENT_DIR`: Directory for storing documents
64+
- `CACHE_DIR`: Directory for storing cache
65+
- `DOWNLOAD_DIR`: Directory for storing downloads
66+
67+
### Methods
68+
69+
#### `exists(path: string): Promise<boolean>`
70+
71+
Checks if a file or directory exists at the specified path.
72+
73+
```typescript
74+
const exists = await NitroFS.exists('/path/to/file')
75+
```
76+
77+
#### `writeFile(path: string, data: string, encoding: NitroFileEncoding): Promise<void>`
78+
79+
Writes data to a file at the specified path.
80+
81+
```typescript
82+
await NitroFS.writeFile('/path/to/file', 'Hello, world!', 'utf8')
83+
```
84+
85+
#### `readFile(path: string, encoding: NitroFileEncoding): Promise<string>`
86+
87+
Reads the contents of a file at the specified path.
88+
89+
```typescript
90+
const data = await NitroFS.readFile('/path/to/file', 'utf8')
91+
```
92+
93+
#### `copyFile(srcPath: string, destPath: string): Promise<void>`
94+
95+
Copies a file from source path to destination path.
96+
97+
```typescript
98+
await NitroFS.copyFile('/path/to/file', '/path/to/destination')
99+
```
100+
101+
#### `copy(srcPath: string, destPath: string): Promise<void>`
102+
103+
Copies a file or directory from source path to destination path.
104+
105+
```typescript
106+
await NitroFS.copy('/path/to/file', '/path/to/destination')
107+
```
108+
109+
#### `unlink(path: string): Promise<boolean>`
110+
111+
Deletes a file or directory from the file system.
112+
113+
```typescript
114+
await NitroFS.unlink('/path/to/file')
115+
```
116+
117+
#### `mkdir(path: string): Promise<boolean>`
118+
119+
Creates a directory in the file system.
120+
121+
```typescript
122+
await NitroFS.mkdir('/path/to/directory')
123+
```
124+
125+
#### `stat(path: string): Promise<NitroFileStat>`
126+
127+
Gets the stat information of a file or directory.
128+
129+
```typescript
130+
const stat = await NitroFS.stat('/path/to/file')
131+
```
132+
133+
#### `uploadFile(file: NitroFile, uploadOptions: NitroUploadOptions, onProgress?: (uploadedBytes: number, totalBytes: number) => void): Promise<void>`
134+
135+
Uploads a file to a server with progress tracking.
136+
137+
```typescript
138+
const options: NitroUploadOptions = {
139+
file: {
140+
name: 'test.txt',
141+
mimeType: 'text/plain',
142+
path: 'test.txt',
143+
},
144+
url: 'https://example.com/upload',
145+
headers: {
146+
'X-Filename': 'test.txt',
147+
},
148+
}
149+
150+
await NitroFS.uploadFile(options, (uploadedBytes, totalBytes) => {
151+
console.log(`Uploading ${(uploadedBytes / totalBytes) * 100}%`)
152+
})
153+
```
154+
155+
#### `downloadFile(serverUrl: string, fileName: string, destinationPath: string, onProgress?: (downloadedBytes: number, totalBytes: number) => void): Promise<NitroFile>`
156+
157+
Downloads a file from a server to the specified destination path with progress tracking.
158+
159+
```typescript
160+
const serverUrl = 'https://example.com/download'
161+
const fileName = 'file.txt'
162+
const destinationPath = NitroFS.DOWNLOAD_DIR + '/file.txt'
163+
164+
const file = await NitroFS.downloadFile(
165+
serverUrl,
166+
fileName,
167+
destinationPath,
168+
(downloadedBytes, totalBytes) => {
169+
console.log(`Downloading ${(downloadedBytes / totalBytes) * 100}%`)
170+
}
171+
)
172+
```
173+
174+
### Types
175+
176+
#### `NitroFile`
177+
178+
```typescript
179+
interface NitroFile {
180+
name: string
181+
mimeType: string
182+
path: string
183+
}
184+
```
185+
186+
#### `NitroUploadOptions`
187+
188+
```typescript
189+
interface NitroUploadOptions {
190+
file: NitroFile
191+
url: string
192+
headers?: Record<string, string>
193+
}
194+
```
195+
196+
#### `NitroFileStat`
197+
198+
Contains file/directory statistics information.
199+
200+
#### `NitroFileEncoding`
201+
202+
Type for file encoding options (e.g., 'utf8').
20203

21204
## Contributing
22205

23206
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
207+
208+
## Credits
209+
210+
Bootstrapped with [create-nitro-module](https://github.com/patrickkabwe/create-nitro-module).
211+
212+
## License
213+
214+
[MIT](LICENSE)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.nitrofs
2+
3+
import android.os.Handler
4+
import android.os.Looper
5+
import io.ktor.client.HttpClient
6+
import io.ktor.client.call.body
7+
import io.ktor.client.engine.okhttp.OkHttp
8+
import io.ktor.client.plugins.onDownload
9+
import io.ktor.client.request.prepareGet
10+
import io.ktor.http.HttpMethod
11+
import io.ktor.util.cio.writeChannel
12+
import io.ktor.utils.io.ByteReadChannel
13+
import io.ktor.utils.io.copyAndClose
14+
import java.io.File
15+
16+
class FileDownloader {
17+
suspend fun downloadFile(
18+
serverUrl: String,
19+
fileName: String,
20+
destinationPath: String,
21+
onProgress: ((Double, Double) -> Unit)?
22+
) {
23+
val outputFile = File(destinationPath)
24+
outputFile.parentFile?.mkdirs()
25+
val client = HttpClient(OkHttp)
26+
27+
client.use { it
28+
it.prepareGet("$serverUrl/$fileName") {
29+
method = HttpMethod.Get
30+
onDownload { bytesSentTotal, contentLength ->
31+
if (bytesSentTotal > 0){
32+
onProgress?.let {
33+
Handler(Looper.getMainLooper()).post {
34+
onProgress.invoke(bytesSentTotal.toDouble(), contentLength.toDouble())
35+
}
36+
}
37+
}
38+
}
39+
}.execute { response ->
40+
val channel: ByteReadChannel = response.body()
41+
channel.copyAndClose(outputFile.writeChannel())
42+
}
43+
}
44+
}
45+
}

android/src/main/java/com/nitrofs/HybridNitroFS.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,18 @@ class HybridNitroFS: HybridNitroFSSpec() {
128128
destinationPath: String,
129129
onProgress: ((Double, Double) -> Unit)?
130130
): Promise<NitroFile> {
131-
TODO("Not yet implemented")
131+
return Promise.async {
132+
try {
133+
nitroFsImpl.downloadFile(serverUrl, fileName, destinationPath, onProgress)
134+
NitroFile(
135+
name = fileName,
136+
path = destinationPath,
137+
mimeType = "",
138+
)
139+
} catch (e: Exception) {
140+
Log.e("NitroFS", "Error downloading file: ${e.message}")
141+
throw Error(e)
142+
}
143+
}
132144
}
133145
}

android/src/main/java/com/nitrofs/NitroFSImpl.kt

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
11
package com.nitrofs
22

3-
import android.Manifest
43
import android.content.Context
5-
import android.content.pm.PackageManager
6-
import android.net.Uri
74
import android.os.Environment
85
import android.util.Log
9-
import androidx.core.app.ActivityCompat
10-
import androidx.core.content.ContextCompat
116
import com.facebook.react.bridge.ReactApplicationContext
12-
import com.margelo.nitro.NitroModules
13-
import com.margelo.nitro.core.Promise
147
import com.margelo.nitro.nitrofs.NitroFile
158
import com.margelo.nitro.nitrofs.NitroFileEncoding
169
import com.margelo.nitro.nitrofs.NitroFileStat
@@ -21,6 +14,7 @@ import java.nio.charset.Charset
2114

2215
class NitroFSImpl(val context: ReactApplicationContext) {
2316
private val nitroFileUploader: NitroFileUploader = NitroFileUploader()
17+
private val fileDownloader: FileDownloader = FileDownloader()
2418

2519
fun exists(path: String): Boolean {
2620
val dir = File(path)
@@ -88,13 +82,26 @@ class NitroFSImpl(val context: ReactApplicationContext) {
8882
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)?.absolutePath ?: ""
8983
}
9084

91-
suspend fun uploadFile(nitroFile: NitroFile,
85+
suspend fun uploadFile(file: NitroFile,
9286
uploadOptions: NitroUploadOptions,
93-
onProgress: ((Double, Double) -> Unit)?) {
94-
val nitroFile = File(nitroFile.path)
87+
onProgress: ((Double, Double) -> Unit)?
88+
) {
89+
val nitroFile = File(file.path)
90+
nitroFileUploader.handleUpload(nitroFile, uploadOptions, onProgress)
91+
}
9592

96-
Log.d("NitroFS", "Uploading file: ${nitroFile.absolutePath}")
97-
nitroFileUploader.handleUpload(nitroFile,uploadOptions, onProgress)
93+
suspend fun downloadFile(
94+
serverUrl: String,
95+
fileName: String,
96+
destinationPath: String,
97+
onProgress: ((Double, Double) -> Unit)?
98+
) {
99+
fileDownloader.downloadFile(
100+
serverUrl,
101+
fileName,
102+
destinationPath,
103+
onProgress
104+
)
98105
}
99106

100107
fun getFileEncoding(encoding: NitroFileEncoding): Charset {

0 commit comments

Comments
 (0)