This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
# Architecture
|
||||
|
||||
## Boundaries
|
||||
|
||||
```text
|
||||
Android picker / content URI / single-file open-with
|
||||
|
|
||||
DocumentStore (Android)
|
||||
metadata -> size-limited private snapshot -> detector
|
||||
|
|
||||
+----------------+------------------+
|
||||
| | |
|
||||
PDF images text / packages
|
||||
| | |
|
||||
read-only PFD ImageLoader LocalPreviewLoader
|
||||
| (Android bitmap) | |
|
||||
isolated Service bytes JvmZipArchive
|
||||
| | |
|
||||
bounded raw ARGB +------+-----+
|
||||
| |
|
||||
PdfRenderClient common Kotlin core
|
||||
| XML / Office / CSV / Markdown
|
||||
| models / policy / search / export
|
||||
+------------------- Compose UI -------------+
|
||||
```
|
||||
|
||||
Native PDF data travels through granted file descriptors, not provider URIs or filesystem paths.
|
||||
The isolated worker cannot browse the app's private files. The host validates dimensions, byte
|
||||
length and page metadata before copying raw pixels to a bitmap. There is no PNG decoder in that
|
||||
return path. General image decoding still runs in the host process, and common parsers run on an
|
||||
IO dispatcher in the host; do not describe all parsing as process-isolated.
|
||||
|
||||
## Why Kotlin Multiplatform first, not a new C++ format stack
|
||||
|
||||
The current reusable work is format identification, reading-preview extraction, policy, models,
|
||||
search and export. It needs neither Android APIs nor a handwritten native Office rendering engine.
|
||||
Those pieces live in `core/src/commonMain`, which also enables direct JVM tests. Kotlin common code
|
||||
can be compiled for Apple targets without passing Android URI or Bitmap types through the API.
|
||||
|
||||
A later LibreOfficeKit or other native renderer can be a separate native adapter. This does not
|
||||
require the pure common core or native user interfaces to be rewritten in C++. Conversely, the JVM
|
||||
ZIP implementation is explicitly **not** portable to iOS; an equivalent Apple adapter is required.
|
||||
|
||||
## Common model
|
||||
|
||||
- `FileClassifier` combines bounded header evidence, name/MIME hints and package entry inspection.
|
||||
- `SafetyPolicy` centralizes ceilings and is shared by readers and archive adapters.
|
||||
- `ArchiveContainer` exposes metadata and bounded part reads; it must never resolve external URLs.
|
||||
- `FormatHandler` is the internal extension seam for package-reading preview handlers.
|
||||
- `DocumentPreview` declares its fidelity, section/table/block data, warnings and archive metadata.
|
||||
- `PreviewSafety` limits aggregate output amplification, including repeated shared strings.
|
||||
- `PreviewText` performs literal searches and a text export; no formula, script or macro execution.
|
||||
- `CoreApi` provides a small public boundary useful to a native host.
|
||||
|
||||
`SafeXml` is a bounded preview-specific XML subset, not a validating standards-complete XML library.
|
||||
It rejects DTD/custom entities and verifies basic syntax/nesting. It does not resolve namespace
|
||||
URIs, validate schemas, establish signature trust or guarantee complete OOXML/ODF semantics.
|
||||
Fuzzing and comparative testing against well-maintained parsers remain release gates.
|
||||
|
||||
## Android lifecycle
|
||||
|
||||
`ViewerViewModel` owns the current document, page bitmap, reading preview, recents and loading/error
|
||||
state. Rotation retains the ViewModel; saved provider URI/page state allows process-restoration
|
||||
attempts. External attachment grants may expire, so reselection is an explicit recovery path.
|
||||
|
||||
A processing mutex serializes active document operations. New opens cancel earlier requests; input
|
||||
copy loops observe coroutine cancellation, and stale staged files are cleaned. Common parsing is
|
||||
bounded but not a hard-preemptible sandbox. PDF rendering requests have a timeout; cancellation and
|
||||
worker death need further real-device stress testing.
|
||||
|
||||
Snapshots use random internal names. Display names are sanitized separately. The original provider
|
||||
file is never opened for writing. Share exports go into a separate narrow FileProvider directory;
|
||||
that provider is not exported. Save/preview-export callbacks check the expected document identity
|
||||
so a new incoming document does not silently replace the intended export.
|
||||
|
||||
## Storage contract
|
||||
|
||||
The native browser lists user-chosen document trees through `SafBrowserStore`. The system picker
|
||||
still supplies user-mediated directory grants and standalone files. There is no whole-device crawl,
|
||||
proprietary cloud or direct WebDAV/SFTP client. The app has no network permission; a provider may.
|
||||
|
||||
`BrowserViewModel` owns locations, visited breadcrumbs, options, selection and provider jobs, separate
|
||||
from `ViewerViewModel`. Shared `BrowserEntry` / `BrowserLocation` / `DirectoryReader` types, sorting,
|
||||
MIME/selection rules and `SelectionCopyBudget` live in commonMain. Android URI values are opaque IDs,
|
||||
not parsed filesystem paths in the core. A saveable Compose state holder retains browser scroll
|
||||
when the viewer replaces that screen. Provider metadata work has bounded workers and cancellation.
|
||||
|
||||
`FilePickerActivity` is a separate standard-launch activity. It accepts GET_CONTENT and returns only
|
||||
user-confirmed, bounded snapshots via `WorkbenchFileProvider`, a narrow FileProvider subclass that
|
||||
preserves MIME metadata. A single read grant applies to each selected copy, never to a source tree.
|
||||
Only the requested standard extras survive activity-intent sanitization before ViewModel creation.
|
||||
System OPEN_DOCUMENT is used for standalone selection, preventing GET_CONTENT picker recursion.
|
||||
|
||||
Folder bookmarks own tree grants. Document-history eviction only releases separate document grants.
|
||||
The per-process active-snapshot set protects a live MainActivity preview when a picker viewer opens;
|
||||
explicit clear-cache remains an intentional global invalidation of exported copies. Registry/cache
|
||||
cleanup and blocked-stream cancellation still require device stress tests.
|
||||
|
||||
Persistent recents store URI, display name, persistence flag and timestamp, up to 20 entries. No
|
||||
content indexing is performed across unopened files. Cache files are plaintext inside the Android
|
||||
sandbox; cache deletion is logical deletion, not guaranteed secure erasure.
|
||||
Reference in New Issue
Block a user