# react-native-local-download

Simple download folder access for react-native

Like my projects? Support me on ko-fi!

<a href='https://ko-fi.com/W7W7X8T7W' target='_blank'><img height='42' style='border:0px;height:42px;' src='https://storage.ko-fi.com/cdn/kofi6.png?v=6' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>

This package implements a few utility functions used in ChatterUI:

- Saving files to the android Downloads folder
- FileDescriptor access
- Persist access to SAF content
- Copying files directly from SAF

This package is intended to be used with other file-system packages such as `expo-file-system` or `react-native-fs`.

This was not tested on lower Android SDK APIs, might not work on Android 9 and below.

## Installation

```sh
npm install @vali98/react-native-fs
```

## API Reference

### `localDownload(uri: string, never_ask_function?: () => void): Promise<void>`

Saves a file to the system Downloads folder.

**Parameters:**
- `uri` - Path to the file to download (file:// URI)
- `never_ask_function` - (Optional) Callback triggered if user permanently denies storage permission

**Platform Support:** Android & iOS

**Example:**
```ts
import { localDownload } from '@vali98/react-native-fs'

// Basic usage
await localDownload('/path/to/file.txt')

// With permission callback
await localDownload('/path/to/file.txt', () => {
  console.log('User denied permissions permanently')
})
```

---

### `copyFileSAF(sourceUri: string, destPath: string, onProgress?: (progress: CopyProgress) => void): Promise<void>`

Copies a file from a Storage Access Framework (SAF) URI to a local file path. Provides real-time progress updates.

**Parameters:**
- `sourceUri` - SAF content:// URI of the source file
- `destPath` - Destination file:// path where the file will be copied
- `onProgress` - (Optional) Callback function called with progress updates

**Progress Object:**
```ts
interface CopyProgress {
  bytesCopied: number        // Bytes copied so far
  totalBytes: number         // Total file size
  percentComplete: number    // 0-100 percentage
  bytesPerSec: number       // Transfer speed in bytes/second
}
```

**Platform Support:** Android only

**Errors:**
- Throws `TypeError` if sourceUri is not a content:// URI
- Rejects with `COPY_ERROR` if file copy fails
- Rejects with `COPY_PERMISSION_DENIED` if access is denied

**Example:**
```ts
import { copyFileSAF, type CopyProgress } from '@vali98/react-native-fs'

await copyFileSAF(
  contentUri,
  '/path/to/app/private/file.txt',
  (progress: CopyProgress) => {
    const speedMB = (progress.bytesPerSec / (1024 * 1024)).toFixed(2)
    console.log(`${progress.percentComplete.toFixed(1)}% - ${speedMB} MB/s`)
  }
)
```

---

### `getContentFd(uri: string): Promise<string>`

Gets a file descriptor from a content:// URI. Useful for advanced file operations.

**Parameters:**
- `uri` - SAF content:// URI

**Returns:** File descriptor as a string

**Platform Support:** Android only

**Errors:**
- Throws `TypeError` if uri is not a content:// URI
- Rejects with `FD_OPEN_FAILED` if file descriptor cannot be opened

**Example:**
```ts
import { getContentFd } from '@vali98/react-native-fs'

const fd = await getContentFd(contentUri)
// Use fd for native operations...
```

---

### `closeFd(fd: string): Promise<void>`

Closes a file descriptor obtained from `getContentFd()`.

**Parameters:**
- `fd` - File descriptor string or path

**Platform Support:** Android only

**Example:**
```ts
import { getContentFd, closeFd } from '@vali98/react-native-fs'

const fd = await getContentFd(contentUri)
// Use fd...
await closeFd(fd)
```

---

### `persistContentPermission(uri: string): Promise<void>`

Persists access permission to a SAF URI, allowing future access without the user needing to re-grant permissions.

**Parameters:**
- `uri` - SAF content:// URI to persist

**Platform Support:** Android only

**Errors:**
- Rejects with `PERSIST_PERMISSION_DENIED` if the URI doesn't support persistent access

**Example:**
```ts
import { persistContentPermission } from '@vali98/react-native-fs'

// After user picks a file via SAF
await persistContentPermission(selectedContentUri)
// Later, can access the same URI without re-prompting
```

---

### `requestStoragePermission(): Promise<PermissionStatus>`

Manually request storage permissions. Automatically handles Android API level differences (Android 13+ uses per-file permissions).

**Returns:** One of `'granted'`, `'denied'`, or `'never_ask_again'`

**Platform Support:** Android only (returns 'granted' on iOS)

**Example:**
```ts
import { requestStoragePermission } from '@vali98/react-native-fs'

const result = await requestStoragePermission()
if (result === 'granted') {
  // Proceed with file operations
}
```

---

## Types

### `CopyProgress`

```ts
interface CopyProgress {
  bytesCopied: number      // Bytes copied so far
  totalBytes: number       // Total file size in bytes
  percentComplete: number  // 0-100 percentage complete
  bytesPerSec: number     // Transfer speed in bytes/second
}
```

## Contributing

See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.

## License

MIT

---

Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
