docs: Rechercheergebnisse zu Ordner-Dialog und Konfigurationsablage uebernehmen
Beide Recherchen lagen bisher nur auf lokalen Wegwerf-Branches. Die Kurzfassung steht an den Tickets #2 und #3, die Langfassungen sind mit Quellenangaben und einer Trennung zwischen Belegtem und Vermutetem aber das, worauf man zurueckkommt, wenn spaeter jemand fragt, warum unter Windows Local statt Roaming gewaehlt wurde. Damit sind research/ordner-dialog und research/config-ablageort ueberfluessig und koennen geloescht werden.
This commit is contained in:
565
docs/research/konfigurations-ablageort.md
Normal file
565
docs/research/konfigurations-ablageort.md
Normal file
@@ -0,0 +1,565 @@
|
||||
# Ablageort für die anwendungsweite Konfiguration
|
||||
|
||||
**Gitea-Issue:** #3 (Part of #1) · **Stand der Recherche:** 2026-08-27
|
||||
**Umfeld:** DioNotes, Rust (edition 2021), Dioxus 0.7.1 mit Default-Feature `desktop`.
|
||||
Entwicklung unter Windows 11; **Linux wird als Zielplattform mitgedacht**, macOS nachrangig.
|
||||
|
||||
Alle Aussagen sind als **[belegt]** (Primärquelle, URL angegeben) oder **[Vermutung]**
|
||||
(Schlussfolgerung, Erfahrungswert, nicht durch eine Quelle abgedeckt) markiert.
|
||||
|
||||
---
|
||||
|
||||
## 1. Kurzantwort
|
||||
|
||||
Für die Liste zuletzt benutzter Workspaces:
|
||||
|
||||
**Pfad unter Windows (ausgeschrieben):**
|
||||
|
||||
```
|
||||
C:\Users\<Benutzername>\AppData\Local\DioNotes\config\recent-workspaces.json
|
||||
```
|
||||
|
||||
**Pfad unter Linux (ausgeschrieben):**
|
||||
|
||||
```
|
||||
/home/<benutzername>/.local/state/dionotes/recent-workspaces.json
|
||||
```
|
||||
|
||||
Nein, das ist kein Tippfehler und keine Inkonsistenz: Linux hat für „zuletzt benutzt"-Listen
|
||||
mit `$XDG_STATE_HOME` einen *eigenen* Ort, den Windows und macOS nicht kennen. Die XDG-Spec
|
||||
nennt „recently used files" wörtlich als Beispiel. Begründung und Beleg in Abschnitt 4.2 —
|
||||
ein einziger Rust-Ausdruck deckt beide Fälle ab.
|
||||
|
||||
**Rust-Aufruf, der ihn liefert** (Crate `directories` 6.0.0):
|
||||
|
||||
```rust
|
||||
use directories::ProjectDirs;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn recent_workspaces_file() -> std::io::Result<PathBuf> {
|
||||
let dirs = ProjectDirs::from("", "", "DioNotes").ok_or_else(|| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"Home-Verzeichnis des Benutzers nicht ermittelbar",
|
||||
)
|
||||
})?;
|
||||
|
||||
// state_dir() existiert nur unter Linux (XDG_STATE_HOME) — genau dort gehört
|
||||
// eine "zuletzt benutzt"-Liste hin. Windows/macOS liefern None, dort ist
|
||||
// config_local_dir() der richtige Ort. Siehe Abschnitt 3 und 4.
|
||||
let dir = dirs.state_dir().unwrap_or_else(|| dirs.config_local_dir());
|
||||
|
||||
fs::create_dir_all(dir)?; // muss die App selbst tun, siehe Abschnitt 5
|
||||
Ok(dir.join("recent-workspaces.json"))
|
||||
}
|
||||
```
|
||||
|
||||
Derselbe Aufruf liefert plattformübergreifend:
|
||||
|
||||
| Plattform | `state_dir().unwrap_or_else(\|\| config_local_dir())` | Herkunft |
|
||||
|---|---|---|
|
||||
| Windows | `C:\Users\<Benutzername>\AppData\Local\DioNotes\config` | `config_local_dir()` (`state_dir()` ist `None`) |
|
||||
| Linux | `$XDG_STATE_HOME/dionotes` bzw. `~/.local/state/dionotes` | `state_dir()` |
|
||||
| macOS | `~/Library/Application Support/DioNotes` | `config_local_dir()` (`state_dir()` ist `None`) |
|
||||
|
||||
Zum Vergleich, falls doch eine *echte* Einstellung (Theme, Schriftgröße) dazukommt — die gehört
|
||||
nach `config_local_dir()` bzw. `config_dir()`:
|
||||
|
||||
| Plattform | `config_local_dir()` | `config_dir()` |
|
||||
|---|---|---|
|
||||
| Windows | `…\AppData\Local\DioNotes\config` | `…\AppData\Roaming\DioNotes\config` |
|
||||
| Linux | `~/.config/dionotes` | `~/.config/dionotes` *(identisch)* |
|
||||
| macOS | `~/Library/Application Support/DioNotes` | `~/Library/Application Support/DioNotes` *(identisch)* |
|
||||
|
||||
**[belegt]** — empirisch verifiziert: Ein Wegwerf-Crate mit `directories = "6"` unter Windows 11
|
||||
ausgeführt liefert für `ProjectDirs::from("", "", "DioNotes")`:
|
||||
|
||||
```
|
||||
project_path = "DioNotes"
|
||||
config_dir = "C:\Users\marvin.dietermann\AppData\Roaming\DioNotes\config"
|
||||
config_local_dir = "C:\Users\marvin.dietermann\AppData\Local\DioNotes\config"
|
||||
data_local_dir = "C:\Users\marvin.dietermann\AppData\Local\DioNotes\data"
|
||||
cache_dir = "C:\Users\marvin.dietermann\AppData\Local\DioNotes\cache"
|
||||
state_dir = None
|
||||
chosen (state_dir OR config_local_dir) = "C:\Users\marvin.dietermann\AppData\Local\DioNotes\config"
|
||||
```
|
||||
|
||||
Die Linux-/macOS-Werte stammen aus dem Quelltext von `directories` 6.0.0 (nicht auf den
|
||||
Plattformen selbst ausgeführt) — siehe Abschnitt 4.
|
||||
|
||||
---
|
||||
|
||||
## 2. Welche Crate? (`dirs` vs. `directories`)
|
||||
|
||||
### Die beiden Kandidaten
|
||||
|
||||
Beide stammen vom selben Autor, teilen sich die Low-Level-Schicht `dirs-sys` und sind seit
|
||||
**2025-01-12 gemeinsam auf Version 6.0.0**. **[belegt]** — crates.io-API,
|
||||
<https://crates.io/api/v1/crates/dirs> und <https://crates.io/api/v1/crates/directories>:
|
||||
|
||||
| Crate | Version | Release-Datum | Downloads gesamt | Downloads (90 Tage) |
|
||||
|---|---|---|---|---|
|
||||
| `dirs` | 6.0.0 | 2025-01-12 | ~307,9 Mio. | ~72,4 Mio. |
|
||||
| `directories` | 6.0.0 | 2025-01-12 | ~67,1 Mio. | ~11,9 Mio. |
|
||||
| `dirs-sys` | 0.5.0 | 2025-01-11 | ~319,0 Mio. | ~76,5 Mio. |
|
||||
|
||||
Beide sind damit **der übliche Weg** im Rust-Ökosystem; alles andere ist eine Nische.
|
||||
|
||||
**Arbeitsteilung** **[belegt]** — README von `dirs`:
|
||||
> „If your requirements are more complex, e.g. computing cache, config, etc. paths for specific
|
||||
> applications or projects, consider using `directories` instead."
|
||||
>
|
||||
> <https://codeberg.org/dirs/dirs-rs> (README, gespiegelt unter
|
||||
> <https://raw.githubusercontent.com/soc/dirs-rs/main/README.md>)
|
||||
|
||||
- `dirs` = flache Funktionen, liefern nur das *Basisverzeichnis* (`dirs::config_dir()` →
|
||||
`%APPDATA%`). Den App-Namen hängt man selbst an.
|
||||
- `directories` = `ProjectDirs`, rechnet den anwendungsspezifischen Unterpfad plattformgerecht
|
||||
aus (Windows: `Organisation\App\config`, Linux: kleingeschrieben, macOS: Bundle-ID-Form).
|
||||
|
||||
### Wartungszustand — die wichtigste Einschränkung
|
||||
|
||||
**[belegt]** Die GitHub-Repos sind **archiviert**. `github.com/dirs-dev/directories-rs` zeigt:
|
||||
„This repository was archived by the owner on Feb 18, 2025. It is now read-only." und verweist
|
||||
auf <https://codeberg.org/dirs/directories-rs>.
|
||||
Quellen: <https://github.com/dirs-dev/directories-rs>, GitHub-API `repos/dirs-dev/directories-rs`
|
||||
(`archived: true`).
|
||||
|
||||
**[belegt]** Die Codeberg-Repos sind **nicht** archiviert und sind der aktive Ort.
|
||||
Codeberg-API `repos/dirs/directories-rs`: `archived=false`, `updated_at=2026-03-22`.
|
||||
Die Commit-Historie zeigt seit dem Release 6.0.0 aber **nur noch Metadaten-Commits**:
|
||||
|
||||
```
|
||||
2026-03-20 Update README.md
|
||||
2025-08-26 Update README to reflect migration from GitHub to Codeberg
|
||||
2025-04-29 Update Cargo.toml to reflect migration from GitHub to Codeberg
|
||||
2025-01-12 Release 6.0.0
|
||||
```
|
||||
|
||||
Quelle: <https://codeberg.org/api/v1/repos/dirs/directories-rs/commits> (identisches Bild bei
|
||||
`dirs/dirs-rs`).
|
||||
|
||||
**Interpretation [Vermutung]:** Die Crates sind nicht verwaist, aber funktional
|
||||
fertig — seit über 1,5 Jahren keine Code-Änderung. Für „gib mir einen Pfad" ist das kein
|
||||
Problem; die Known-Folder-/XDG-Konventionen ändern sich nicht. Ein Risiko entstünde erst, wenn
|
||||
`windows-sys` o. ä. eine Breaking-Change-Welle auslöst.
|
||||
|
||||
### Aktiv entwickelte Alternative
|
||||
|
||||
`etcetera` 0.11.0, veröffentlicht 2025-10-28, MSRV 1.87. **[belegt]** — crates.io-API,
|
||||
<https://crates.io/crates/etcetera>, Repo <https://github.com/lunacookies/etcetera>.
|
||||
Bietet explizit wählbare „Strategien" (native vs. XDG-überall).
|
||||
**[Vermutung]:** Für DioNotes kein Vorteil — die Extra-Flexibilität wird nicht gebraucht,
|
||||
und die Verbreitung ist geringer.
|
||||
|
||||
### Empfehlung
|
||||
|
||||
**`directories` 6.0.0.** Begründung:
|
||||
|
||||
1. Es nimmt einem die plattformspezifische Namensform ab (Windows `DioNotes`, Linux `dionotes`,
|
||||
macOS `DioNotes` als Bundle-ID-Fragment) — genau die Sorte Detail, die man sonst falsch macht.
|
||||
2. **[belegt]** `dirs` 6.0.0 ist **bereits transitiv im Dependency-Graph** des Projekts:
|
||||
`Cargo.lock` listet `dirs 6.0.0` und `dirs-sys 0.5.0`, hereingezogen über `wry` und
|
||||
`tray-icon` (beide über `dioxus-desktop`). Quelle: `Cargo.lock` im Repo, Einträge
|
||||
`name = "dirs"` (Z. 2915) sowie die Dependency-Listen von `tray-icon` und `wry`.
|
||||
`directories` hat als einzige Nicht-Dev-Dependency `dirs-sys ^0.5.0`
|
||||
(<https://crates.io/api/v1/crates/directories/6.0.0/dependencies>) — das Hinzufügen kostet
|
||||
also genau **eine** zusätzlich kompilierte Crate.
|
||||
3. **[belegt]** Das offizielle Dioxus-SDK verwendet für seinen Storage selbst `directories`:
|
||||
```rust
|
||||
directories::BaseDirs::new().unwrap().data_local_dir().to_path_buf()
|
||||
```
|
||||
<https://github.com/DioxusLabs/sdk/blob/main/packages/storage/src/client_storage/fs.rs>
|
||||
|
||||
4. **[belegt]** Es liefert `state_dir()`, das unter Linux auf `$XDG_STATE_HOME` zeigt und auf
|
||||
Windows/macOS `None` ist — siehe Abschnitt 4.2. `dirs` hat unter Windows zwar auch ein
|
||||
`state_dir()` (das dort `None` liefert), aber ohne `ProjectDirs` muss man den App-Namen und
|
||||
dessen plattformspezifische Schreibweise wieder selbst zusammenbauen.
|
||||
|
||||
Mit `dirs::config_local_dir()` käme man unter Windows genauso weit — dann aber ohne
|
||||
`config`-Unterordner, ohne den XDG-State-Pfad und **ohne die Linux-Kleinschreibung**: Man
|
||||
erzeugte `~/.config/DioNotes` statt `~/.config/dionotes`. Da Linux ausdrücklich mitgedacht werden
|
||||
soll, fällt `dirs` allein damit aus.
|
||||
|
||||
---
|
||||
|
||||
## 3. Welcher Pfad ist der richtige — und unter Windows: Roaming oder Local?
|
||||
|
||||
### Was die Crate zurückgibt
|
||||
|
||||
**[belegt]** — Quelltext `directories` 6.0.0, `src/win.rs` (aus dem lokalen
|
||||
Cargo-Registry-Checkout, entspricht <https://codeberg.org/dirs/directories-rs>):
|
||||
|
||||
```rust
|
||||
let config_dir = app_data_roaming.join("config");
|
||||
let config_local_dir = app_data_local.join("config");
|
||||
let data_dir = app_data_roaming.join("data");
|
||||
```
|
||||
|
||||
Und `dirs` 6.0.0, `src/win.rs`:
|
||||
|
||||
```rust
|
||||
pub fn cache_dir() -> Option<PathBuf> { data_local_dir() }
|
||||
pub fn config_dir() -> Option<PathBuf> { dirs_sys::known_folder_roaming_app_data() }
|
||||
pub fn config_local_dir() -> Option<PathBuf> { dirs_sys::known_folder_local_app_data() }
|
||||
pub fn data_dir() -> Option<PathBuf> { dirs_sys::known_folder_roaming_app_data() }
|
||||
pub fn data_local_dir() -> Option<PathBuf> { dirs_sys::known_folder_local_app_data() }
|
||||
pub fn preference_dir() -> Option<PathBuf> { dirs_sys::known_folder_local_app_data() }
|
||||
```
|
||||
|
||||
> ⚠️ **Fallstrick:** Die README-Tabelle von `dirs` führt `preference_dir` unter Windows als
|
||||
> `{FOLDERID_RoamingAppData}`. Der Quelltext und die Laufzeitausgabe sagen
|
||||
> **LocalAppData**. **[belegt]** durch beides (Quelltext oben; Testlauf lieferte
|
||||
> `dirs::preference_dir() = Some("C:\Users\...\AppData\Local")`). Im Zweifel gilt der Code.
|
||||
> `preference_dir` ist ohnehin nur auf macOS ein eigener Ort und dort *nicht* zu benutzen
|
||||
> (siehe unten).
|
||||
|
||||
Nicht der richtige Pfad ist also:
|
||||
- `cache_dir()` — löschbar, darf jederzeit verschwinden.
|
||||
- `data_dir()` / `data_local_dir()` — für Nutzdaten der App, nicht für Einstellungen.
|
||||
- `preference_dir()` — auf macOS `~/Library/Preferences`, und dort darf man laut Apple gar
|
||||
nicht selbst hineinschreiben (siehe Abschnitt 4).
|
||||
|
||||
Richtig ist `config_dir()` **oder** `config_local_dir()`.
|
||||
|
||||
### Roaming vs. Local — die Windows-Konvention
|
||||
|
||||
**[belegt]** Beide Known Folders, Default-Pfade laut Microsoft
|
||||
(<https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid>):
|
||||
|
||||
| KNOWNFOLDERID | GUID | Display Name | Default Path | CSIDL |
|
||||
|---|---|---|---|---|
|
||||
| `FOLDERID_RoamingAppData` | `{3EB685DB-65F9-4CF6-A03A-E3EF65729F3D}` | Roaming | `%APPDATA%` (`%USERPROFILE%\AppData\Roaming`) | `CSIDL_APPDATA` |
|
||||
| `FOLDERID_LocalAppData` | `{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}` | Local | `%LOCALAPPDATA%` (`%USERPROFILE%\AppData\Local`) | `CSIDL_LOCAL_APPDATA` |
|
||||
|
||||
**[belegt]** Die Bedeutung, wörtlich aus dem Microsoft-Deployment-Guide
|
||||
(<https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-vista/cc766489(v=ws.10)>):
|
||||
|
||||
> „Windows uses the Local and LocalLow folders for application data that does not roam with the
|
||||
> user. **Usually this data is either machine specific or too large to roam.**"
|
||||
|
||||
> „Windows uses the Roaming folder for application specific data, such as custom dictionaries,
|
||||
> **which are machine independent and should roam with the user profile.**"
|
||||
|
||||
**[belegt]** Dieselbe Unterscheidung, aktuell gepflegt, in der .NET-API-Doku
|
||||
(<https://learn.microsoft.com/en-us/dotnet/api/system.environment.specialfolder>, `ms.date` 2025-07,
|
||||
zuletzt aktualisiert 2026-05):
|
||||
|
||||
> `ApplicationData` — „The directory that serves as a common repository for application-specific
|
||||
> data **for the current roaming user.** A roaming user works on more than one computer on a
|
||||
> network. A roaming user's profile is kept on a server on the network and is loaded onto a
|
||||
> system when the user logs on."
|
||||
>
|
||||
> `LocalApplicationData` — „The directory that serves as a common repository for
|
||||
> application-specific data that is used by the current, **non-roaming** user."
|
||||
|
||||
**[belegt]** Und die alte CSIDL-Doku
|
||||
(<https://learn.microsoft.com/en-us/windows/win32/shell/csidl>):
|
||||
|
||||
> `CSIDL_LOCAL_APPDATA` — „The file system directory that serves as a data repository for local
|
||||
> **(nonroaming)** applications."
|
||||
|
||||
### Das Entscheidungskriterium — und die Anwendung auf DioNotes
|
||||
|
||||
Die Konvention fragt nicht „Konfiguration oder Daten?", sondern **„maschinenunabhängig oder
|
||||
maschinenspezifisch?"**:
|
||||
|
||||
- **maschinenunabhängig** (Theme, Schriftgröße, Tastaturkürzel, benutzerdefiniertes Wörterbuch)
|
||||
→ `%APPDATA%` (Roaming) → `config_dir()`
|
||||
- **maschinenspezifisch oder groß** (absolute Dateipfade, Fensterpositionen, Fenstergrößen,
|
||||
Caches, Indizes) → `%LOCALAPPDATA%` (Local) → `config_local_dir()`
|
||||
|
||||
**Die Liste zuletzt benutzter Workspaces besteht aus absoluten Dateisystempfaden.** Ein
|
||||
`D:\Notizen\Privat` existiert auf dem Laptop im Homeoffice schlicht nicht. Sie ist damit
|
||||
per Definition maschinenspezifisch und gehört nach **`%LOCALAPPDATA%`**.
|
||||
Das ist eine direkte Anwendung des oben zitierten Microsoft-Satzes — **[belegt]** in der
|
||||
Prämisse, **[Vermutung]** nur insofern, als Microsoft DioNotes nicht namentlich erwähnt.
|
||||
|
||||
**Empfehlung [Vermutung, gut begründet]:** Vorerst *alles* nach `config_local_dir()` legen —
|
||||
also `%LOCALAPPDATA%\DioNotes\config\`. Gründe:
|
||||
|
||||
1. Der einzige heute existierende Konfigurationsinhalt (die Workspace-Liste) ist eindeutig
|
||||
maschinenspezifisch.
|
||||
2. Roaming hat reale Fehlermodi, die man sich ohne Not einhandelt: Der Roaming-Ordner wird bei
|
||||
Domänen-Profilen erst beim Ab-/Anmelden synchronisiert, wodurch bei paralleler Nutzung
|
||||
zweier Rechner die letzte Abmeldung gewinnt und Änderungen der anderen Sitzung überschreibt.
|
||||
(Mechanik des Roaming User Profile: <https://learn.microsoft.com/en-us/windows-server/storage/folder-redirection/deploy-roaming-user-profiles>)
|
||||
3. Zwei Verzeichnisse für eine Handvoll Einstellungen sind Overhead. Ein zweiter Ort
|
||||
(`config_dir()` → `%APPDATA%\DioNotes\config\`) lohnt sich erst, wenn eine echte
|
||||
maschinenunabhängige Einstellung existiert, die roamen *soll*.
|
||||
|
||||
**Wichtig für die Portabilität:** Diese Roaming-/Local-Entscheidung ist **rein Windows-spezifisch
|
||||
und kostet auf den anderen Plattformen nichts**. **[belegt]** — Quelltext `directories` 6.0.0:
|
||||
unter Linux ist `config_local_dir = config_dir.clone()` (`src/lin.rs`), unter macOS ebenso
|
||||
(`src/mac.rs`). `config_local_dir()` zu wählen verbaut also unter Linux/macOS keine Option;
|
||||
es ist dort schlicht derselbe Pfad.
|
||||
|
||||
---
|
||||
|
||||
## 4. Linux und macOS
|
||||
|
||||
> Ursprünglich als Nebenfrage angelegt — Linux hat sich beim Nachrecherchieren aber als der
|
||||
> Fall herausgestellt, der die Empfehlung tatsächlich verändert. Siehe 4.2.
|
||||
|
||||
### 4.1 Wohin `ProjectDirs` zeigt
|
||||
|
||||
**[belegt]** Quelltext `directories` 6.0.0:
|
||||
|
||||
`src/lin.rs`:
|
||||
```rust
|
||||
pub fn project_dirs_from(_qualifier: &str, _organization: &str, application: &str) -> Option<ProjectDirs> {
|
||||
ProjectDirs::from_path(PathBuf::from(&trim_and_lowercase_then_replace_spaces(application, "")))
|
||||
}
|
||||
// config_dir = $XDG_CONFIG_HOME (sonst $HOME/.config) + project_path
|
||||
// config_local_dir = config_dir.clone() ← unter Linux identisch!
|
||||
```
|
||||
|
||||
`src/mac.rs`:
|
||||
```rust
|
||||
pub fn project_dirs_from(qualifier: &str, organization: &str, application: &str) -> Option<ProjectDirs> {
|
||||
let organization = organization.replace(" ", "-");
|
||||
let application = application.replace(" ", "-");
|
||||
let mut parts = vec![qualifier, &organization, &application]; parts.retain(|e| !e.is_empty());
|
||||
let bundle_id = parts.join(".");
|
||||
ProjectDirs::from_path(PathBuf::from(bundle_id))
|
||||
}
|
||||
// config_dir = $HOME/Library/Application Support + project_path
|
||||
// preference_dir = $HOME/Library/Preferences + project_path
|
||||
```
|
||||
|
||||
Daraus folgt für `ProjectDirs::from("", "", "DioNotes")`:
|
||||
- **Linux:** `~/.config/dionotes` — `config_dir()` und `config_local_dir()` sind dort *dasselbe*.
|
||||
Die Roaming-Frage stellt sich unter Linux nicht.
|
||||
- **macOS:** `~/Library/Application Support/DioNotes` (leerer Qualifier/Organisation werden aus
|
||||
der Bundle-ID herausgefiltert).
|
||||
|
||||
**[belegt]** Die Linux-Zuordnung deckt sich mit der XDG Base Directory Specification
|
||||
(<https://specifications.freedesktop.org/basedir/latest/>):
|
||||
> „`$XDG_CONFIG_HOME` defines the base directory relative to which user-specific configuration
|
||||
> files should be stored. If `$XDG_CONFIG_HOME` is either not set or empty, a default equal to
|
||||
> `$HOME`/.config should be used."
|
||||
|
||||
**[belegt]** Für macOS ist `Application Support` — nicht `Preferences` — korrekt. Apple, File
|
||||
System Programming Guide
|
||||
(<https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html>):
|
||||
|
||||
> `Library/Application Support` — „Use this directory to store all app data files except those
|
||||
> associated with the user's documents. For example, you might use this directory to store
|
||||
> app-created data files, **configuration files**, templates … All content in this directory
|
||||
> should be placed in a custom subdirectory whose name is that of your app's bundle identifier
|
||||
> or your company."
|
||||
>
|
||||
> `Library/Preferences` — „This directory contains app-specific preference files. **You should
|
||||
> not create files in this directory yourself.** Instead, use the `NSUserDefaults` class or
|
||||
> CFPreferences API…"
|
||||
|
||||
Das ist der Grund, `preference_dir()` **nicht** zu benutzen: Es zeigt auf macOS genau in den
|
||||
Ordner, in den man laut Apple nicht selbst schreiben soll.
|
||||
|
||||
### 4.2 Linux kennt einen dritten Ort — und der ist für unseren Fall der richtige
|
||||
|
||||
XDG hat neben `config` und `data` noch `$XDG_STATE_HOME`. **[belegt]** — XDG Base Directory
|
||||
Specification **Version 0.8** (08. Mai 2021), <https://specifications.freedesktop.org/basedir/latest/>:
|
||||
|
||||
> „`$XDG_STATE_HOME` defines the base directory relative to which user-specific state files
|
||||
> should be stored. If `$XDG_STATE_HOME` is either not set or empty, a default equal to
|
||||
> `$HOME`/.local/state should be used."
|
||||
|
||||
Und zum Inhalt, wörtlich:
|
||||
|
||||
> „The `$XDG_STATE_HOME` contains state data that should persist between (application) restarts,
|
||||
> but that is not important or portable enough to the user that it should be stored in
|
||||
> `$XDG_DATA_HOME`. It may contain:
|
||||
> - actions history (logs, history, **recently used files**, …)
|
||||
> - current state of the application that can be reused on a restart (view, layout,
|
||||
> **open files**, undo history, …)"
|
||||
|
||||
Das ist keine Auslegung — die Spezifikation nennt „recently used files" und „open files"
|
||||
**namentlich**. Eine Liste zuletzt benutzter Workspaces ist unter Linux genau das: State,
|
||||
nicht Config. Sie gehört nach `~/.local/state/dionotes`, nicht nach `~/.config/dionotes`.
|
||||
|
||||
**[belegt]** `directories` 6.0.0 bildet das ab, und zwar nur dort, wo es existiert:
|
||||
|
||||
| Plattform | Quelltext | `ProjectDirs::state_dir()` |
|
||||
|---|---|---|
|
||||
| Linux | `src/lin.rs` Z. 73/84: `$XDG_STATE_HOME` bzw. `$HOME/.local/state`, dann `.join(project_path)`, `state_dir: Some(state_dir)` | `Some("~/.local/state/dionotes")` |
|
||||
| Windows | `src/win.rs` Z. 89: `state_dir: None` | `None` |
|
||||
| macOS | `src/mac.rs` Z. 83: `state_dir: None` | `None` |
|
||||
|
||||
Empirisch auf Windows 11 bestätigt: `state_dir() = None`, und
|
||||
`state_dir().unwrap_or_else(|| config_local_dir())` liefert
|
||||
`C:\Users\…\AppData\Local\DioNotes\config`.
|
||||
|
||||
**Konsequenz für den Aufruf [Vermutung, gut begründet]:** Ein einziger Ausdruck ist auf allen drei
|
||||
Plattformen richtig, weil der Fallback genau dann greift, wenn das Betriebssystem den Begriff
|
||||
„State" gar nicht kennt:
|
||||
|
||||
```rust
|
||||
let dir = dirs.state_dir().unwrap_or_else(|| dirs.config_local_dir());
|
||||
```
|
||||
|
||||
Das ist bewusst *kein* `#[cfg(target_os = ...)]` — die Fallunterscheidung steckt bereits im
|
||||
`Option`, das `directories` zurückgibt.
|
||||
|
||||
### 4.3 Weitere Linux-Punkte
|
||||
|
||||
**[belegt] Kleinschreibung wird automatisch erledigt.** `src/lin.rs` reicht den Anwendungsnamen
|
||||
durch `trim_and_lowercase_then_replace_spaces` — aus `"DioNotes"` wird `dionotes`. Genau das ist
|
||||
unter Linux üblich; mit `dirs` (statt `directories`) müsste man das von Hand tun und würde sonst
|
||||
ein unschönes `~/.config/DioNotes` erzeugen. Das ist das stärkste einzelne Argument für
|
||||
`directories` statt `dirs`.
|
||||
|
||||
**[belegt] Flatpak/Snap funktionieren ohne Sonderbehandlung.** `directories` liest die
|
||||
XDG-Variablen zur Laufzeit aus (`env::var_os("XDG_CONFIG_HOME")`, `…("XDG_STATE_HOME")`,
|
||||
jeweils gefiltert durch `dirs_sys::is_absolute_path`, `src/lin.rs` Z. 12–19 bzw. 66–73). Eine
|
||||
Sandbox, die diese Variablen umbiegt (Flatpak setzt sie auf `~/.var/app/<app-id>/…`), wird damit
|
||||
automatisch respektiert.
|
||||
**[Vermutung]:** Das eigentliche Sandbox-Problem läge woanders — die in der Liste gespeicherten
|
||||
absoluten Workspace-Pfade zeigen ggf. aus der Sandbox heraus. Das ist aber eine Frage des
|
||||
Workspace-Zugriffs, nicht des Ablageorts der Liste.
|
||||
|
||||
**[belegt] Der `HOME`-Fallback ist robust.** Ist `$XDG_STATE_HOME` nicht gesetzt oder kein
|
||||
absoluter Pfad, greift `$HOME/.local/state`. `ProjectDirs::from(...)` liefert nur dann `None`,
|
||||
wenn sich gar kein Home-Verzeichnis ermitteln lässt — der Fehlerfall ist also derselbe wie unter
|
||||
Windows und muss ohnehin behandelt werden.
|
||||
|
||||
---
|
||||
|
||||
## 5. Muss die App das Verzeichnis anlegen?
|
||||
|
||||
**Ja, den anwendungsspezifischen Teil.**
|
||||
|
||||
**[belegt]** README von `directories` und `dirs`, wörtlich identisch:
|
||||
> „This library does not create directories or check for their existence. The library only
|
||||
> provides information on what the path to a certain directory **should** be."
|
||||
>
|
||||
> <https://codeberg.org/dirs/directories-rs> · <https://codeberg.org/dirs/dirs-rs>
|
||||
|
||||
**[belegt]** Empirisch auf dem Entwicklungsrechner: Der Testlauf zeigte
|
||||
`"C:\Users\...\AppData\Local\DioNotes" exists = false` und
|
||||
`"C:\Users\...\AppData\Roaming\DioNotes" exists = false` — `ProjectDirs` liefert also einen Pfad,
|
||||
den es nicht gibt.
|
||||
|
||||
**[belegt]** Der Fall ist im Ökosystem üblich: Das Dioxus-SDK ruft direkt nach dem Ermitteln des
|
||||
Pfads `std::fs::create_dir_all(path).unwrap()` auf
|
||||
(<https://github.com/DioxusLabs/sdk/blob/main/packages/storage/src/client_storage/fs.rs>).
|
||||
|
||||
**[belegt]** Die Basis-Ordner `%APPDATA%` / `%LOCALAPPDATA%` selbst existieren dagegen: Sie sind
|
||||
registrierte Known Folders vom Typ `PERUSER` und werden mit dem Benutzerprofil angelegt
|
||||
(<https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid>). Anzulegen ist also nur
|
||||
der Teil `DioNotes\config`.
|
||||
|
||||
**[belegt]** Unter Linux erwartet auch die XDG-Spec, dass die App das tut:
|
||||
> „When attempting to write a file, if the destination directory is non-existent an attempt
|
||||
> should be made to create it with permission `0700`."
|
||||
|
||||
Praktisch: `std::fs::create_dir_all(dir)?` beim Start bzw. vor dem ersten Schreiben.
|
||||
`create_dir_all` ist idempotent und liefert `Ok(())`, wenn das Verzeichnis schon existiert.
|
||||
|
||||
---
|
||||
|
||||
## 6. Gibt es in Dioxus 0.7 eine eigene Abstraktion dafür?
|
||||
|
||||
**Nein.** Vier unabhängige Belege:
|
||||
|
||||
1. **[belegt]** `dioxus_desktop::Config` hat genau zwei Pfad-Methoden — `with_resource_directory`
|
||||
und `with_data_directory` — und keine davon liefert ein OS-Konfigurationsverzeichnis.
|
||||
<https://docs.rs/dioxus-desktop/0.7.1/dioxus_desktop/struct.Config.html>
|
||||
|
||||
2. **[belegt] `with_data_directory` ist eine Falle.** Es klingt richtig, ist aber etwas anderes.
|
||||
Quelltext, Tag `v0.7.1`
|
||||
(<https://github.com/DioxusLabs/dioxus/blob/v0.7.1/packages/desktop/src/config.rs>):
|
||||
```rust
|
||||
/// set the directory where data will be stored in release mode.
|
||||
///
|
||||
/// > Note: This **must** be set when bundling on Windows.
|
||||
pub fn with_data_directory(mut self, path: impl Into<PathBuf>) -> Self {
|
||||
self.data_dir = Some(path.into());
|
||||
self
|
||||
}
|
||||
```
|
||||
Verwendet wird `data_dir` an genau einer Stelle
|
||||
(<https://github.com/DioxusLabs/dioxus/blob/v0.7.1/packages/desktop/src/webview.rs>, Z. 262):
|
||||
```rust
|
||||
let mut web_context = WebContext::new(cfg.data_dir.clone());
|
||||
```
|
||||
Und `wry::WebContext` ist das *Browser-Profil* der WebView:
|
||||
> „A context that is shared between multiple WebViews. A browser would have a context for all
|
||||
> the normal tabs and a different context for all the private/incognito tabs."
|
||||
> `data_directory`: „Whether the WebView window should have a custom user data path. This is
|
||||
> useful in Windows when a bundled application can't have the webview data inside Program Files."
|
||||
>
|
||||
> <https://docs.rs/wry/latest/wry/struct.WebContext.html>
|
||||
|
||||
Das ist der Ort für WebView2-Cookies/LocalStorage/Cache, **nicht** für die Konfiguration der
|
||||
Anwendung.
|
||||
|
||||
3. **[belegt]** Die Item-Liste von `dioxus` 0.7.1 (<https://docs.rs/dioxus/0.7.1/dioxus/all.html>)
|
||||
enthält nichts Passendes. `dioxus-cli-config` betrifft Build-/Serve-Parameter der CLI,
|
||||
`SyncStorage`/`UnsyncStorage` sind Signal-Backends, kein Festplattenspeicher.
|
||||
Zwei unabhängige Doku-Korpora (`dioxuslabs.com/learn/0.7` als llms-full sowie die Docsite)
|
||||
liefern auf die Frage nach einem Konfigurationsverzeichnis keinen Treffer.
|
||||
Auch die repo-eigene `AGENTS.md` (Dioxus-0.7-API-Referenz) enthält dazu nichts.
|
||||
|
||||
4. **[belegt]** Das offizielle **Dioxus-SDK** greift für seinen `use_persistent`-Storage selbst zu
|
||||
`directories` — wäre in Dioxus etwas eingebaut, hätte es das benutzt.
|
||||
<https://github.com/DioxusLabs/sdk/blob/main/packages/storage/src/client_storage/fs.rs>
|
||||
|
||||
**Zu `dioxus-sdk` / `dioxus-storage` als Alternative [Vermutung]:** Es wäre ebenfalls eine
|
||||
Extra-Dependency (aktuell auf Dioxus 0.6 ausgerichtet, `dioxus-sdk = "0.6"`), legt alles nach
|
||||
`BaseDirs::data_local_dir()` — also `%LOCALAPPDATA%` **ohne** App-Unterordner — und bringt einen
|
||||
kompletten Hook-Apparat mit. Für eine einzelne JSON-Datei ist das mehr Maschinerie als Nutzen.
|
||||
|
||||
---
|
||||
|
||||
## 7. Offene Punkte / bewusst nicht geklärt
|
||||
|
||||
- **[nicht geprüft]** Verhalten unter Windows, wenn DioNotes als MSIX/Store-App verpackt würde
|
||||
(dann greift Filesystem-Virtualisierung und `%LOCALAPPDATA%` wird umgeleitet). Für
|
||||
`dx bundle` als klassische Desktop-App ist das irrelevant.
|
||||
- **[nicht geprüft]** Die Linux-/macOS-Pfade wurden aus dem Quelltext von `directories` 6.0.0
|
||||
abgeleitet und sind dort eindeutig, aber nicht auf den Plattformen selbst ausgeführt. Sobald
|
||||
ein Linux-Build existiert, sollte man den ermittelten Pfad einmal gegenprüfen.
|
||||
- **[nicht geprüft]** Verhalten unter Flatpak/Snap in der Praxis. Der Mechanismus ist klar
|
||||
(XDG-Variablen werden zur Laufzeit gelesen und damit von der Sandbox respektiert), aber nicht
|
||||
in einer echten Sandbox getestet. Ungeklärt ist dort separat, ob die gespeicherten absoluten
|
||||
Workspace-Pfade aus der Sandbox heraus überhaupt erreichbar sind — eigenes Thema.
|
||||
- **[offen, Folgeentscheidung]** Ob `state_dir()` auch für spätere echte Einstellungen benutzt
|
||||
wird oder ob dafür ein zweiter Ort (`config_local_dir()`) aufgemacht wird. Unter Linux wären
|
||||
das zwei getrennte Verzeichnisse (`~/.local/state/dionotes` und `~/.config/dionotes`), unter
|
||||
Windows/macOS fielen sie zusammen. Solange es nur die Workspace-Liste gibt, stellt sich die
|
||||
Frage nicht.
|
||||
- **[offen, Folgeentscheidung]** Dateiformat und Schema der Workspace-Liste (JSON? TOML?
|
||||
Migrationsstrategie bei Formatänderung?) — das ist eine eigene Frage und nicht Teil dieses
|
||||
Tickets.
|
||||
|
||||
---
|
||||
|
||||
## 8. Quellenverzeichnis
|
||||
|
||||
**Microsoft (Primärquellen für die Windows-Konvention)**
|
||||
- KNOWNFOLDERID: <https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid>
|
||||
- CSIDL: <https://learn.microsoft.com/en-us/windows/win32/shell/csidl>
|
||||
- `Environment.SpecialFolder`: <https://learn.microsoft.com/en-us/dotnet/api/system.environment.specialfolder>
|
||||
- Managing Roaming User Data Deployment Guide: <https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-vista/cc766489(v=ws.10)>
|
||||
- Deploy roaming user profiles: <https://learn.microsoft.com/en-us/windows-server/storage/folder-redirection/deploy-roaming-user-profiles>
|
||||
|
||||
**Plattform-Konventionen Linux / macOS**
|
||||
- XDG Base Directory Specification, Version 0.8 (08.05.2021) — `$XDG_CONFIG_HOME`,
|
||||
`$XDG_STATE_HOME`, Anlegen mit `0700`: <https://specifications.freedesktop.org/basedir/latest/>
|
||||
- Apple, File System Programming Guide: <https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html>
|
||||
|
||||
**Crates**
|
||||
- `directories` (docs): <https://docs.rs/directories/6.0.0/directories/struct.ProjectDirs.html>
|
||||
- `directories` (Quelltext, aktiv): <https://codeberg.org/dirs/directories-rs>
|
||||
- `dirs` (Quelltext, aktiv): <https://codeberg.org/dirs/dirs-rs>
|
||||
- Archivierte GitHub-Spiegel: <https://github.com/dirs-dev/directories-rs>, <https://github.com/dirs-dev/dirs-rs>
|
||||
- crates.io-Metadaten: <https://crates.io/crates/dirs>, <https://crates.io/crates/directories>, <https://crates.io/crates/etcetera>
|
||||
|
||||
**Dioxus**
|
||||
- `dioxus_desktop::Config`: <https://docs.rs/dioxus-desktop/0.7.1/dioxus_desktop/struct.Config.html>
|
||||
- `packages/desktop/src/config.rs` @ v0.7.1: <https://github.com/DioxusLabs/dioxus/blob/v0.7.1/packages/desktop/src/config.rs>
|
||||
- `packages/desktop/src/webview.rs` @ v0.7.1: <https://github.com/DioxusLabs/dioxus/blob/v0.7.1/packages/desktop/src/webview.rs>
|
||||
- `wry::WebContext`: <https://docs.rs/wry/latest/wry/struct.WebContext.html>
|
||||
- Dioxus SDK Storage: <https://github.com/DioxusLabs/sdk/blob/main/packages/storage/src/client_storage/fs.rs>
|
||||
Reference in New Issue
Block a user