Skip to content

Commit 64ca95c

Browse files
committed
docs: add readme to explain the core:file module
1 parent 85a9fd5 commit 64ca95c

File tree

1 file changed

+120
-0
lines changed

1 file changed

+120
-0
lines changed

core/file/README.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Thunderbird Core File Module
2+
3+
This module provides a simple, consistent API for common file operations across Android and JVM platforms.
4+
5+
## Architecture
6+
7+
The file system layer is split into two levels:
8+
9+
- Public low-level I/O: `FileSystemManager` opens `RawSource`/`RawSink` for a given `Uri`.
10+
- Android actual: `AndroidFileSystemManager`
11+
- JVM actual: `JvmFileSystemManager`
12+
- Public high-level facade: `FileManager` for common operations (currently: copy).
13+
- Default implementation: `DefaultFileManager` delegating to internal commands
14+
- Internal commands: e.g., `CopyCommand(source, dest)` implement operations using `FileSystemManager`.
15+
- Hidden from public API; return `Outcome<Unit, FileOperationError>` internally to preserve error context.
16+
- `RawSource`/`RawSink` come from `kotlinx-io` and are referenced in the public API.
17+
18+
### Core Components
19+
20+
```mermaid
21+
classDiagram
22+
class FileManager {
23+
+copy(source: Uri, dest: Uri): Outcome<Unit, FileOperationError>
24+
}
25+
26+
class DefaultFileManager {
27+
-fs: FileSystemManager
28+
}
29+
30+
class FileSystemManager {
31+
+openSource(uri: Uri): RawSource?
32+
+openSink(uri: Uri, mode: WriteMode = WriteMode.Truncate): RawSink?
33+
}
34+
35+
class CopyCommand {
36+
-source: Uri
37+
-destination: Uri
38+
+invoke(fs: FileSystemManager): Outcome<Unit, FileOperationError>
39+
}
40+
41+
class FileOperationError {
42+
}
43+
44+
DefaultFileManager ..> FileSystemManager
45+
CopyCommand --> FileSystemManager
46+
DefaultFileManager ..> CopyCommand : delegates
47+
```
48+
49+
## Getting Started
50+
51+
### Dependency setup
52+
53+
Add the module to your Gradle build. Then, depending on your platform, provide an actual `FileSystemManager` and wire a `FileManager`:
54+
55+
```kotlin
56+
// Koin example (Android)
57+
single<FileSystemManager> { AndroidFileSystemManager(androidContext().contentResolver) }
58+
single<FileManager> { DefaultFileManager(get()) }
59+
```
60+
61+
For JVM-only tools/tests:
62+
63+
```kotlin
64+
val fs: FileSystemManager = JvmFileSystemManager()
65+
val fileManager: FileManager = DefaultFileManager(fs)
66+
```
67+
68+
## Public API
69+
70+
- FileManager
71+
- `suspend fun copy(sourceUri: Uri, destinationUri: Uri): Outcome<Unit, FileOperationError>`
72+
- FileSystemManager
73+
- `fun openSource(uri: Uri): RawSource?`
74+
- `fun openSink(uri: Uri, mode: WriteMode = WriteMode.Truncate): RawSink?`
75+
- Behavior:
76+
- Sinks default to overwrite/truncate. Pass `WriteMode.Append` to append where supported.
77+
- Returns null when the URI cannot be opened (e.g., missing permissions, unsupported scheme).
78+
- Thread-safety: Implementations are stateless and safe to use from multiple threads, but the returned streams must be used/closed by the caller.
79+
- `enum class WriteMode { Truncate, Append }`
80+
81+
## URI type
82+
83+
The API uses a KMP‑friendly `Uri` type (com.eygraber.uri.Uri). On Android, convert a platform URI using the provided extension:
84+
85+
```kotlin
86+
val kmpUri = androidUri.toKmpUri()
87+
```
88+
89+
To build URIs in tests or common code, you can parse a string:
90+
91+
```kotlin
92+
val source = "file:///path/to/file.txt".toKmpUri()
93+
```
94+
95+
## Supported URIs (by platform)
96+
97+
- Android (AndroidFileSystemManager):
98+
- `content://` via `ContentResolver`
99+
- `file://` via `ContentResolver`
100+
- JVM (JvmFileSystemManager):
101+
- `file://` URIs only (non-`file:` schemes are not supported and will return null).
102+
- iOS: No actual yet in this repository, but the API is compatible. An iOS actual can use `NSFileManager`/`NSURL`.
103+
104+
## Error handling best practices
105+
106+
- `openSource(uri)`/`openSink(uri)` return null on failure. Always check for null and handle gracefully (e.g., show a message, request permissions).
107+
- On Android, failures are frequently due to missing URI permissions; prefer SAF pickers and persist permissions when needed.
108+
109+
## Performance and buffering
110+
111+
- Internal copy uses a buffered loop (`BUFFER_SIZE = 8_192L`).
112+
- Streams are flushed and closed to avoid leaks.
113+
- Public `openSource`/`openSink` are not suspending; perform I/O on an appropriate dispatcher/thread when needed.
114+
115+
## Limitations and notes
116+
117+
- Android: Ensure the app holds read/write permissions for the target URI (e.g., via SAF and optionally `takePersistableUriPermission`).
118+
- JVM: Only `file:` URIs are supported by `JvmFileSystemManager`.
119+
- iOS: No actual yet. The public API is prepared for an iOS actual using `NSFileManager`/`NSURL`.
120+

0 commit comments

Comments
 (0)