Files
dionotes-client/docs/research/papierkorb.md
MDietermann d470756920 docs: Rechercheergebnisse zum Notiz-Handling uebernehmen
Beide Recherchen zur Map #16 lagen nur auf lokalen Wegwerf-Branches. Die
Kurzfassungen stehen an den Tickets #21 und #22, die Langfassungen sind mit
Quellen und der Trennung zwischen Belegtem, Abgeleitetem und Unbelegtem aber
das, worauf man zurueckkommt.

Beide enthalten Messungen, die keine Dokumentation hergibt: dass trash 5.2.6
auf Netzlaufwerken gar nicht funktioniert und Windows dort sonst still und
endgueltig loeschen wuerde, und dass ein ungeprunter Append-Log im
Yjs-Benchmark das Siebzigfache des Rohtexts misst.

Ausserdem /.claude in die .gitignore - dort liegen lokale Einstellungen und
die temporaeren Worktrees der Recherche-Agenten.
2026-08-28 12:48:20 +02:00

576 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Papierkorb unter Rust: Crate, Netzlaufwerke, Kosten
**Gitea-Issue:** #21 · **Stand der Recherche:** 2026-08-28
**Umfeld:** DioNotes, Rust (edition 2021), Dioxus 0.7.1, Default-Feature `desktop`.
Entwicklung und Messung unter Windows 11 Enterprise 10.0.26200, cargo/rustc 1.96.0.
Alle Aussagen sind als **[belegt]** (Primärquelle mit URL, Quellcode, oder eigene reproduzierbare
Messung) oder **[Vermutung]** (Schlussfolgerung ohne deckende Quelle) markiert.
> **Besonderheit dieser Recherche:** Die Kernfrage — was auf Netzlaufwerken passiert — wurde
> **nicht erschlossen, sondern gemessen**. Auf dem Entwicklungsrechner sind SMB-Netzlaufwerke
> gemountet (`Z:` → `\\la-nas-01.ad.datawin.de\Scan`). Es wurden drei kleine Wegwerf-Programme
> außerhalb des Repos gebaut und ausgeführt. Die Rohausgaben stehen in Abschnitt 4.
> Im Repo wurde **keine Dependency hinzugefügt**; `Cargo.toml` und `Cargo.lock` sind unverändert.
---
## 1. Kurzantwort
**Die Crate ist `trash`, aktuell `5.2.6`** (veröffentlicht 2026-05-03). Sie ist der De-facto-Standard
und wird gepflegt.
**Aufruf:**
```rust
trash::delete(&pfad)?; // eine Datei
trash::delete_all(&pfade)?; // mehrere
```
**Kosten:** **+6 Pakete** im Windows-Graphen. Das ist **nicht** über Cargo-Features reduzierbar —
das im Ticket vermutete Gegenstück zum `rfd`/Wayland-Fall **gibt es hier nicht**. Fünf der sechs
Pakete sind eine **zweite, ältere Kopie der `windows`-Crate-Familie** (0.56 neben dem bereits
vorhandenen 0.61.3), weil `trash 5.2.6` noch auf `windows 0.56.0` festgenagelt ist.
**Netzlaufwerke — und hier wird es unangenehm, es sind zwei getrennte Probleme:**
1. **`trash 5.2.6` funktioniert auf Netzlaufwerken schlicht gar nicht.** Der Aufruf schlägt mit
einer irreführenden Meldung fehl (`0x80070002`, „Das System kann die angegebene Datei nicht
finden"), **die Datei bleibt unangetastet liegen**. Ursache ist ein Pfad-Bug in der Crate, kein
Papierkorb-Thema. Immerhin: **nichts geht verloren.**
2. **Wäre der Bug behoben, wäre das Verhalten gefährlich.** Ruft man dieselbe Windows-API mit
denselben Flags, aber unverbogenem Pfad auf, dann wird die Datei auf dem Netzlaufwerk
**endgültig und stillschweigend gelöscht** — `PerformOperations()` liefert `Ok(())`,
`GetAnyOperationsAborted()` liefert `false`. Am Rückgabewert ist der Fall **nicht** vom
erfolgreichen Papierkorb-Verschieben zu unterscheiden.
**Die Antwort auf die eigentliche Ticketfrage lautet also: Ja, die App braucht eine Vorab-Prüfung
bzw. Rückfrage.** Sich auf `Ok(())` zu verlassen, reicht nicht.
**Erkennen lässt sich der Fall zuverlässig** — aber nur über `IFileOperationProgressSink`, den
`trash` nicht durchreicht (die Crate übergibt hart `None`). Details in Abschnitt 5.
**Blockierend.** Die Dauer ist unabhängig von der Dateigröße (Verschieben innerhalb des Volumes);
gemessen: ~740 ms beim allerersten Aufruf (COM-Initialisierung), danach ~27 ms. Abschnitt 6.
---
## 2. Welche Crate, wird sie gepflegt?
### [belegt]
- **`trash`, aktuelle Version `5.2.6`**, veröffentlicht **2026-05-03**, nicht zurückgezogen.
2.683.466 Downloads gesamt, 724.229 „recent". Beschreibung: „A library for moving files and
folders to the Recycle Bin".
→ <https://crates.io/api/v1/crates/trash>
- Letzte Releases: 5.2.6 (2026-05-03), 5.2.5 (2025-10-25), 5.2.4 (2025-10-24), 5.2.3 (2025-08-15),
5.2.2 (2025-02-22). Also **regelmäßige Releases, zuletzt vor knapp vier Monaten**.
→ <https://crates.io/api/v1/crates/trash>
- Das in `Cargo.toml` eingetragene Repository ist `https://github.com/ArturKovacs/trash`; gepflegt
und released wird faktisch aus dem Fork **<https://github.com/Byron/trash-rs>** (251 Sterne), auf
den auch die CI-Badges und alle Changelog-Commit-Links zeigen.
→ `trash-5.2.6/README.md`, `trash-5.2.6/CHANGELOG.md`
- Unterstützte Plattformen laut README: „Windows, macOS, and all FreeDesktop Trash compliant
environments (including GNOME, KDE, XFCE, and more)".
→ `trash-5.2.6/README.md`
- `rust-version = "1.85.0"`, `edition = "2021"`, Lizenz MIT.
→ `trash-5.2.6/Cargo.toml`
- Das Changelog von 5.2.6 zeigt echte, inhaltliche Wartung (Panic durch sauberen Fehler ersetzt,
weil `cosmic-files` abstürzte) — die Crate hat reale, anspruchsvolle Nutzer.
→ `trash-5.2.6/CHANGELOG.md`
### [Vermutung]
Es gibt keine ernstzunehmende Alternative. `rrecycle` und `recyclebin` tauchen in der Suche auf,
sind aber ungleich kleiner und teils reine CLI-Werkzeuge. Für DioNotes ist `trash` gesetzt.
---
## 3. Was kostet das im Abhängigkeitsgraphen?
Das Ticket fragt gezielt nach einem Gegenstück zum `rfd`-Fall, wo sich neun Wayland-Pakete durch
Angleichen der Features an `dioxus-desktop` vollständig vermeiden ließen.
### [belegt] Antwort: Ein solches Gegenstück gibt es nicht.
Die Features von `trash 5.2.6` lauten wörtlich:
```toml
[features]
coinit_apartmentthreaded = []
coinit_disable_ole1dde = []
coinit_multithreaded = []
coinit_speed_over_memory = []
default = ["coinit_apartmentthreaded", "chrono"]
```
→ `trash-5.2.6/Cargo.toml`
**Alle vier `coinit_*`-Features haben leere Dependency-Listen** — es sind reine `cfg`-Schalter für
den COM-Initialisierungsmodus (`COINIT_APARTMENTTHREADED` vs. `COINIT_MULTITHREADED`), ohne
jede Wirkung auf den Graphen.
→ `trash-5.2.6/src/windows.rs`, `struct CoInitializer`
Das einzige dependency-wirksame Feature ist `chrono`, und das ist **ausschließlich unter
`cfg(all(unix, not(macos), not(ios), not(android)))`** deklariert. **Auf Windows ist es
wirkungslos.**
→ `trash-5.2.6/Cargo.toml`, Abschnitt `[target.'cfg(all(unix, …))'.dependencies.chrono]`
### [belegt] Gemessene Zahlen
Basis: `Cargo.lock` im Repo mit 745 Paketen; `cargo tree --edges normal` ergibt 419 eindeutige
Pakete für `x86_64-pc-windows-msvc` und 470 für `x86_64-unknown-linux-gnu`.
| Variante | Windows-Target | Linux-Target |
|---|---|---|
| `cargo add trash` (default) | **+6** (419 → 425) | +4 (470 → 474) |
| `cargo add trash --no-default-features` | **+6** (keine Ersparnis) | +2 (470 → 472) |
Die sechs neuen Pakete auf Windows:
```
trash v5.2.6
windows v0.56.0
windows-core v0.56.0
windows-implement v0.56.0 (proc-macro)
windows-interface v0.56.0 (proc-macro)
windows-result v0.1.2
```
### [belegt] Warum die `windows`-Pakete nicht „gratis" sind
Sie stehen zwar bereits namentlich im `Cargo.lock`, aber in **semver-inkompatiblen** Versionen,
werden also **nicht** dedupliziert:
| Crate | schon im Lock (via dioxus/wry) | von `trash` gefordert | dedupliziert? |
|---|---|---|---|
| `windows` | 0.61.3 | **0.56.0** | nein |
| `windows-core` | 0.61.2 | **0.56.0** | nein |
| `windows-implement` | 0.60.2 | **0.56.0** | nein |
| `windows-interface` | 0.59.3 | **0.56.0** | nein |
| `windows-result` | 0.3.4 | **0.1.2** | nein |
`cargo tree -i windows@0.56.0` bestätigt: `trash` ist der einzige Konsument dieser zweiten Kopie.
Wirklich gratis sind nur `log` (0.4.33) und `scopeguard` (1.2.0), die bereits passend im Graphen
stehen.
### [belegt] Auf Linux kostet es fast nichts
Die FreeDesktop-Implementierung ist reines Rust (`libc`, `once_cell`, `scopeguard`, `urlencoding`,
optional `chrono`). **Kein GTK, kein dbus, kein zbus** — der `rfd`-artige Fallstrick existiert hier
nicht.
→ `trash-5.2.6/Cargo.toml`
Mit `--no-default-features` fielen auf Linux `chrono` + `iana-time-zone` weg (+2 statt +4). Preis:
`trash` kann dann keinen `DeletionDate`-Zeitstempel in die `.trashinfo`-Dateien schreiben. Das ist
funktional relevant, nicht bloß kosmetisch. **[Vermutung]** Für DioNotes wäre der Verzicht auf zwei
Pakete den Verlust der Zeitstempel nicht wert.
### [Vermutung] Der einzige echte Hebel
Ein künftiges `trash`-Release, das auf `windows` ≥ 0.61 aktualisiert, würde die gesamte
`windows`-Familie mit dem bestehenden dioxus/wry-Graphen deduplizieren. Der Windows-Delta fiele
dann von 6 auf **1** (nur `trash` selbst). Das ist nichts, worauf man warten sollte, aber es ist der
Grund, die Version im Blick zu behalten.
---
## 4. Netzlaufwerke und USB-Sticks — die Kernfrage
Das Ticket fragt: Fällt der Aufruf still auf endgültiges Löschen zurück, oder schlägt er fehl?
**Die Antwort hat zwei Schichten, und sie sind unabhängig voneinander.**
### 4.1 [belegt] Schicht 1: `trash 5.2.6` schlägt auf Netzlaufwerken fehl — wegen eines Pfad-Bugs
Gemessen, Wegwerf-Programm gegen `trash 5.2.6`, Datei auf `Z:\` (SMB-Share):
```
=== C: Netzlaufwerk SMB, klein | dir=Z:\ | 1024 bytes ===
angelegt: Z:\dionotes-trashprobe-net-small.md
trash::delete -> Err(Os { code: -2147024894,
description: "windows error: Das System kann die angegebene Datei nicht finden. (0x80070002)" })
Dauer: 31.0407ms
Datei existiert noch? true
Papierkorb-Treffer: vorher=0 nachher=0 => IM PAPIERKORB: false
```
Zum Vergleich, dieselbe Operation lokal auf NTFS:
```
=== A: lokal NTFS, klein | dir=C:\…\Temp | 1024 bytes ===
trash::delete -> Ok(())
Dauer: 740.7595ms
Datei existiert noch? false
Papierkorb-Treffer: vorher=0 nachher=1 => IM PAPIERKORB: true
```
**Die Datei auf dem Netzlaufwerk bleibt liegen. Es geht nichts verloren, aber es passiert auch
nichts.** Das reproduziert exakt den seit 2022-08-07 offenen Bug
[trash-rs#55 „Unable to delete file on Windows shared folder"](https://github.com/Byron/trash-rs/issues/55)
(Label `help wanted`, bis heute ungelöst).
**Die Ursache, aus dem Quellcode und nachgemessen:**
`trash` kanonisiert jeden Pfad vor dem Löschen mit `std::fs::canonicalize`:
```rust
let canonical_parent = parent.canonicalize()
.map_err(|_| Error::CanonicalizePath { original: parent.to_owned() })?;
```
→ `trash-5.2.6/src/lib.rs`, `fn canonicalize_paths`
`windows.rs` schneidet danach stumpf die ersten vier UTF-16-Zeichen `\\?\` ab:
```rust
let path_prefix = ['\\' as u16, '\\' as u16, '?' as u16, '\\' as u16];
let wide_path_container = to_wide_path(full_path);
let wide_path_slice = if wide_path_container.starts_with(&path_prefix) {
&wide_path_container[path_prefix.len()..]
} else {
&wide_path_container[0..]
};
```
→ `trash-5.2.6/src/windows.rs`
Das geht für lokale Pfade gut und für UNC-Pfade schief. Gemessen:
```
### canonicalize-Verhalten fuer lokal C:
canonicalize() = "\\?\C:\Users\marvin.dietermann\AppData\Local\Temp"
nach dem Prefix-Stripping = "C:\Users\marvin.dietermann\AppData\Local\Temp" <- gültig
### canonicalize-Verhalten fuer Netzlaufwerk Z:
canonicalize() = "\\?\UNC\la-nas-01.ad.datawin.de\Scan"
nach dem Prefix-Stripping = "UNC\la-nas-01.ad.datawin.de\Scan" <- Unsinn
### canonicalize-Verhalten fuer UNC-Pfad \\la-nas-01.ad.datawin.de\Scan
canonicalize() = "\\?\UNC\la-nas-01.ad.datawin.de\Scan"
nach dem Prefix-Stripping = "UNC\la-nas-01.ad.datawin.de\Scan" <- Unsinn
```
Aus `\\?\UNC\server\share` wird `UNC\server\share` — ein Pfad, den es nicht gibt.
`SHCreateItemFromParsingName` scheitert daran mit `0x80070002`. Korrekt wäre die Rückübersetzung
nach `\\server\share`.
**Betroffen sind beide Schreibweisen** — der gemappte Laufwerksbuchstabe `Z:\` genauso wie der
direkte UNC-Pfad, weil `canonicalize` beide auf dieselbe `\\?\UNC\…`-Form abbildet.
### 4.2 [belegt] Schicht 2: Windows selbst löscht auf Netzlaufwerken still und endgültig
Um Crate-Bug von Betriebssystemverhalten zu trennen, wurde `IFileOperation` **direkt** aufgerufen —
mit exakt denselben Flags wie `trash` (`FOF_NO_UI | FOF_ALLOWUNDO | FOF_WANTNUKEWARNING`,
zusammen `0x00004654`), aber mit unverbogenem Pfad:
```
### IFileOperation direkt auf lokal C:
PerformOperations -> Ok(()) (Dauer 102.5266ms)
GetAnyOperationsAborted -> false
Datei existiert noch? false
Papierkorb-Treffer: 1 <- im Papierkorb
### IFileOperation direkt auf Netzlaufwerk Z:
PerformOperations -> Ok(()) (Dauer 405.4515ms)
GetAnyOperationsAborted -> false
Datei existiert noch? false
Papierkorb-Treffer: 0 <- WEG. endgueltig.
### IFileOperation direkt auf UNC-Pfad \\la-nas-01.ad.datawin.de\Scan
PerformOperations -> Ok(()) (Dauer 299.5401ms)
GetAnyOperationsAborted -> false
Datei existiert noch? false
Papierkorb-Treffer: 0 <- WEG. endgueltig.
```
**Das ist die eigentliche Warnung des Tickets.** Auf dem Netzlaufwerk wird die Datei endgültig
gelöscht, und die API meldet **denselben Erfolg** wie beim Verschieben in den Papierkorb:
`Ok(())` und `GetAnyOperationsAborted() == false`. **Am Rückgabewert ist der Unterschied nicht
erkennbar.**
Bemerkenswert: **`FOF_WANTNUKEWARNING` hat keinen Dialog ausgelöst.** Microsoft dokumentiert das
Flag als „Send a warning if a file or folder is being destroyed during a delete operation rather
than recycled. This flag partially overrides **FOF_NOCONFIRMATION**."
→ <https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifileoperation-setoperationflags>
**[Vermutung]** Die Warnung unterbleibt, weil `trash` zusätzlich `FOF_SILENT` und `FOF_NOERRORUI`
setzt: `FOF_NO_UI` ist in der `windows`-Crate als `1556u32` = `0x614` definiert, also
`FOF_SILENT (0x4) | FOF_NOCONFIRMATION (0x10) | FOF_NOCONFIRMMKDIR (0x200) | FOF_NOERRORUI (0x400)`.
→ `windows-0.56.0/src/Windows/Win32/UI/Shell/mod.rs`, `pub const FOF_NO_UI`
Das „partially overrides" bezieht sich laut Doku nur auf `FOF_NOCONFIRMATION`, nicht auf
`FOF_SILENT`/`FOF_NOERRORUI`. Belegt ist jedenfalls das Ergebnis: **kein Dialog, keine Rückfrage,
Datei weg.**
### 4.3 USB-Sticks und andere Dateisysteme — [nicht belastbar beantwortet]
**Das konnte nicht gemessen werden.** Auf dem Testrechner sind zwar zwei Wechseldatenträger
(`D:`, `E:`) angemeldet, beide sind aber leer (`FileSystemType = Unknown`, `SizeRemaining = 0`) —
offensichtlich Kartenleser ohne Medium. Ohne echten FAT32-/exFAT-Stick lässt sich der Fall nicht
verifizieren.
**[Vermutung]**, ausdrücklich als solche gekennzeichnet: Lokale Wechseldatenträger verhalten sich
anders als Netzlaufwerke; Windows legt auf ihnen üblicherweise sehr wohl einen `$RECYCLE.BIN`-
bzw. `RECYCLER`-Ordner an. Der kritische Fall ist damit **[Vermutung]** primär das Netzlaufwerk.
Verlassen sollte man sich darauf nicht — die in Abschnitt 5 beschriebene Erkennung deckt beide
Fälle ab, ohne dass man vorher wissen muss, welcher vorliegt. **Vor der Implementierung sollte
dieser Fall mit einem echten USB-Stick nachgemessen werden.**
Ebenfalls **[Vermutung]**: Auch auf lokalem NTFS kann der Papierkorb fehlen — er lässt sich pro
Volume in den Papierkorb-Eigenschaften deaktivieren („Dateien sofort löschen"), und Dateien
oberhalb der konfigurierten Maximalgröße gehen ebenfalls direkt verloren. Eine Prüfung, die nur
auf den Laufwerkstyp schaut, greift deshalb zu kurz.
---
## 5. Wie erfährt das Programm, welcher Fall eingetreten ist?
### [belegt] Über den Rückgabewert von `trash`: gar nicht
`trash::delete` liefert `Result<(), Error>`. Die `Error`-Varianten sind `Unknown`, `Os`,
`FileSystem` (nur FreeDesktop), `TargetedRoot`, `CouldNotAccess`, `CanonicalizePath`,
`ConvertOsString`, `RestoreCollision`, `RestoreTwins`.
→ <https://docs.rs/trash/5.2.6/src/trash/lib.rs.html>
**Es gibt keine Variante und kein Erfolgs-Detail, das „endgültig gelöscht statt Papierkorb"
ausdrückt.** Wie Abschnitt 4.2 zeigt, kommt in diesem Fall schlicht `Ok(())` zurück.
Die Crate wertet lediglich `GetAnyOperationsAborted()` aus, und das war im Netzlaufwerk-Fall
`false`:
```rust
pfo.PerformOperations()?;
if pfo.GetAnyOperationsAborted()?.as_bool() {
// TODO: return the reason why the operation was aborted.
return Err(Error::Unknown { description: "Some operations were aborted".into() });
}
```
→ `trash-5.2.6/src/windows.rs`
### [belegt] Über `IFileOperationProgressSink`: zuverlässig — aber `trash` reicht ihn nicht durch
Microsoft dokumentiert für `IFileOperationProgressSink::PostDeleteItem` den Parameter
`psiNewlyCreated` wörtlich so:
> „A pointer to an **IShellItem** that specifies the deleted item, now in the Recycle Bin.
> **If the item was fully deleted, this value is NULL.**"
→ <https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifileoperationprogresssink-postdeleteitem>
Genau das wurde nachgemessen — ein eigener Sink, angehängt an `DeleteItem`:
```
### lokal C:
[sink] PreDeleteItem dwFlags=0x00000282
[sink] PostDeleteItem dwFlags=0x00000282 hrDelete=0x00270008 psiNewlyCreated=Some
==> IM PAPIERKORB: true
neuerPfad=Some("C:\$Recycle.Bin\S-1-5-21-…-1668\$R1V9IFD.md")
### Netzlaufwerk Z:
[sink] PreDeleteItem dwFlags=0x00000202
[sink] PostDeleteItem dwFlags=0x00000202 hrDelete=0x00270008 psiNewlyCreated=NULL
==> IM PAPIERKORB: false
neuerPfad=None
```
**Zwei verwertbare Signale, beide belegt:**
1. **`psiNewlyCreated` in `PostDeleteItem`** — `Some` ⇒ im Papierkorb (inklusive konkretem Pfad im
`$Recycle.Bin`), `NULL` ⇒ endgültig gelöscht. Das ist die verlässliche Nachher-Auskunft.
`hrDelete` taugt **nicht** dafür: es war in beiden Fällen identisch `0x00270008`.
2. **`dwFlags` schon in `PreDeleteItem`, also *vor* dem Löschen.** Die Differenz zwischen `0x282`
und `0x202` ist genau `0x80` — und das ist `TSF_DELETE_RECYCLE_IF_POSSIBLE`:
```
pub const TSF_DELETE_RECYCLE_IF_POSSIBLE: _TRANSFER_SOURCE_FLAGS = _TRANSFER_SOURCE_FLAGS(128i32);
pub const TSF_COPY_LOCALIZED_NAME: _TRANSFER_SOURCE_FLAGS = _TRANSFER_SOURCE_FLAGS(512i32);
pub const TSF_OVERWRITE_EXIST: _TRANSFER_SOURCE_FLAGS = _TRANSFER_SOURCE_FLAGS(2i32);
```
→ `windows-0.56.0/src/Windows/Win32/UI/Shell/mod.rs`
Auf `C:` ist das Bit gesetzt, auf `Z:` fehlt es. **Windows sagt also im `PreDeleteItem`-Callback
von sich aus, ob es recyceln wird** — bevor irgendetwas passiert ist.
Und man kann an dieser Stelle abbrechen: Microsoft dokumentiert für die Sink-Rückgabewerte
„Returns S_OK if successful, or an error value otherwise. In the case of an error value, all
subsequent operations pending from the call to IFileOperation are canceled."
→ <https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifileoperationprogresssink-postdeleteitem>
**[Vermutung]** Damit ließe sich aus `PreDeleteItem` heraus vetoen und stattdessen erst der
Nutzer fragen. Der Abbruch aus `PreDeleteItem` wurde **nicht** gemessen.
**Der Haken:** `trash` übergibt den Sink-Parameter hart als `None`:
```rust
pfo.DeleteItem(&shi, None)?;
```
→ `trash-5.2.6/src/windows.rs`
Über die Crate-API ist an diese Information **nicht** heranzukommen.
### [Vermutung] Konsequenz für DioNotes
Drei gangbare Wege, in aufsteigender Sauberkeit:
1. **Billig und grob:** vor dem Löschen prüfen, ob der Workspace-Pfad auf einem Netzlaufwerk liegt
(Laufwerkstyp `DRIVE_REMOTE` bzw. UNC-Pfad), und dann eine Rückfrage stellen. Erkennt den
Hauptfall, aber nicht den deaktivierten Papierkorb auf lokalem NTFS.
2. **Nachher-Prüfung:** nach `trash::delete` per `trash::os_limited::list()` nachsehen, ob der
Eintrag im Papierkorb aufgetaucht ist. Funktioniert (so wurden die Messungen oben verifiziert),
ist aber langsam und rennt gegen Race Conditions.
3. **Sauber:** `IFileOperation` mit eigenem Sink selbst aufrufen, statt `trash` zu benutzen — dann
bekommt man `TSF_DELETE_RECYCLE_IF_POSSIBLE` vorab und `psiNewlyCreated` hinterher, und der
UNC-Bug aus 4.1 entfällt ebenfalls, weil man `canonicalize` nicht braucht. Kostet dafür eine
eigene Windows-Implementierung und hilft auf Linux/macOS nicht.
Da `trash 5.2.6` auf Netzlaufwerken ohnehin nicht funktioniert, ist die Entscheidung ohne eine
bewusste Behandlung dieses Falls **nicht** vollständig — deshalb ist dieses Ticket die
Voraussetzung für die Lösch-Implementierung, nicht bloß eine Fußnote.
---
## 6. Blockierend oder asynchron? Wie lange dauert es?
### [belegt] Blockierend
`trash::delete` ist eine gewöhnliche synchrone Funktion; es gibt keine `async`-Variante in der
öffentlichen API. Der Windows-Pfad ruft `pfo.PerformOperations()` direkt im aufrufenden Thread auf,
ohne Thread zu spawnen.
→ `trash-5.2.6/src/windows.rs`, <https://docs.rs/trash/5.2.6/src/trash/lib.rs.html>
### [belegt] Gemessene Dauer
| Fall | Größe | Dauer |
|---|---|---|
| lokal NTFS, erster Aufruf im Prozess | 1 KiB | **740,8 ms** |
| lokal NTFS, zweiter Aufruf | **512 MiB** | **26,7 ms** |
| `IFileOperation` direkt, lokal | 5 B | 102,5 ms |
| `IFileOperation` direkt, Netzlaufwerk `Z:` | 5 B | 405,5 ms |
| `IFileOperation` direkt, UNC-Pfad | 5 B | 299,5 ms |
**Die Dateigröße spielt praktisch keine Rolle:** 512 MiB wurden in 26,7 ms erledigt — schneller
als die 1-KiB-Datei davor. Das Verschieben in den Papierkorb ist ein Rename **innerhalb desselben
Volumes**, es werden keine Daten kopiert.
Was kostet, ist der **erste** Aufruf im Prozess: die COM-Initialisierung
(`CoInitializeEx` + `CoCreateInstance` der `FileOperation`-Klasse) schlägt mit gut 0,7 s zu Buche.
`trash` initialisiert COM `thread_local` und einmalig.
→ `trash-5.2.6/src/windows.rs`, `thread_local! { static CO_INITIALIZER … }`
**[Vermutung]** Für DioNotes heißt das: der Aufruf gehört nicht in den UI-Thread. Ein
`spawn_blocking` bzw. der `tokio`-Threadpool genügt völlig; eine Fortschrittsanzeige braucht es
für einzelne `.md`-Dateien nicht. Netzlaufwerke sind mit ~0,3–0,4 s deutlich träger als lokale
Platten, aber immer noch weit unter jeder Schmerzgrenze.
**Achtung [belegt]:** `trash` initialisiert COM standardmäßig mit `COINIT_APARTMENTTHREADED`
(Feature `coinit_apartmentthreaded`, Teil der Defaults) und **panickt**, wenn `CoInitializeEx`
fehlschlägt:
```rust
panic!("Call to CoInitializeEx failed. HRESULT: {:?}. Consider using `trash` with the feature `coinit_multithreaded`", hr);
```
→ `trash-5.2.6/src/windows.rs`
**[Vermutung]** Ruft man `trash` aus einem Tokio-Worker-Thread auf, der bereits als MTA
initialisiert wurde, könnte das zuschlagen; dann ist `default-features = false` plus
`features = ["coinit_multithreaded"]` der Ausweg. Das wurde **nicht** gemessen.
---
## 7. macOS und Linux
### [belegt] macOS
`trash` bietet zwei Verfahren, wählbar über `TrashContext`:
- **`DeleteMethod::Finder` (Default)** — steuert den Finder per AppleScript.
- **`DeleteMethod::NsFileManager`** — ruft `trashItemAtURL` auf `NSFileManager` auf. Laut den
Doc-Kommentaren: „Somewhat faster than the `Finder` method", „Does *not* require additional
permissions", „Does *not* produce the sound that Finder usually makes", aber „Does *not* show the
'Put Back' option on some systems … This is a macOS bug."
→ `trash-5.2.6/src/macos/mod.rs`
Fehler von `trashItemAtURL` werden als `Error::Unknown` weitergereicht, nicht verschluckt.
Abhängigkeiten: `objc2`, `objc2-foundation`, `percent-encoding` — alle drei stehen **bereits im
`Cargo.lock`** (0.6.4 / 0.3.2 / 2.3.2), auf macOS wäre `trash` also nahezu gratis.
### [belegt] Linux / FreeDesktop
Implementiert die FreeDesktop-Trash-Spezifikation v1.0. Die Implementierung ist reines Rust, ohne
GTK/dbus. Wichtig für die Netzlaufwerk-Frage: Der Code **legt die Trash-Ordner bei Bedarf an** und
fällt bei einem geräteübergreifenden Verschieben explizit auf Kopieren-und-Löschen zurück:
```rust
let Err(e) = std::fs::rename(src, dst) else { return Ok(()) };
let needs_cross_device_copy = e.kind() == ErrorKind::CrossesDevices;
if !needs_cross_device_copy { return Err((src.to_owned(), e)); }
debug!("Cross-device move detected, falling back to copy+delete for {:?}", src);
```
→ `trash-5.2.6/src/freedesktop.rs`
**[Vermutung]** Damit ist Linux der unproblematischere Fall: Entweder die Datei landet in einem
Trash-Verzeichnis, oder es kommt ein `Error::FileSystem` zurück. Ein stilles endgültiges Löschen
wie unter Windows ist im Code nicht angelegt. Nicht gemessen — es stand keine Linux-Maschine zur
Verfügung.
---
## 8. Empfehlung
**[Vermutung]** — das ist eine Einschätzung, keine belegte Aussage:
1. **`trash 5.2.6` nehmen**, mit Default-Features. Die +6 Pakete sind der Preis; verhandeln lässt
sich daran nichts.
2. **Den Netzlaufwerk-Fall vor der ersten Löschfunktion klären, nicht danach.** `trash` schlägt
dort heute fehl (4.1) — die App muss diesen Fehler ohnehin abfangen und dem Nutzer erklären,
sonst wirkt das Löschen auf Netzlaufwerken schlicht kaputt.
3. **Eine Rückfrage vor dem Löschen ist nötig**, sobald der Workspace nicht auf einer lokalen
Platte liegt. Der Nutzer muss wissen, ob seine Notiz wiederherstellbar ist — und aus dem
Rückgabewert von `trash` erfährt er es nicht (5).
4. **Nicht im UI-Thread aufrufen** (6).
5. **Die `trash`-Version im Blick behalten**: sobald sie auf `windows` ≥ 0.61 geht, halbieren sich
die Kosten, und `trash-rs#55` könnte bei der Gelegenheit mit erledigt werden.
---
## 9. Reproduktion
Die Messungen stammen aus drei Wegwerf-Programmen, die **außerhalb des Repos** gebaut wurden
(Scratchpad, nicht eingecheckt):
- `main.rs` — `trash::delete` gegen lokal / Netzlaufwerk, mit Papierkorb-Gegenprobe über
`trash::os_limited::list()`
- `raw.rs` — `IFileOperation` direkt, mit denselben Flags, ohne `canonicalize`; zeigt zusätzlich
das Prefix-Stripping-Verhalten
- `sink.rs` — eigener `IFileOperationProgressSink` (`#[implement]` aus `windows 0.56`), protokolliert
`PreDeleteItem`/`PostDeleteItem`
Alle angelegten Testdateien wurden wieder entfernt, die Papierkorb-Einträge mit
`trash::os_limited::purge_all` endgültig gelöscht. Auf `Z:` sind keine Rückstände verblieben.
**Im Repo wurde nichts an `Cargo.toml` oder `Cargo.lock` geändert** — die Dependency-Messung lief
über `cargo add` mit anschließendem `git checkout --`, verifiziert mit leerem
`git status --porcelain`.
---
## 10. Quellen
- crates.io-API zu `trash`: <https://crates.io/api/v1/crates/trash>
- API-Doku: <https://docs.rs/trash/latest/trash/>
- Quelltext `lib.rs` (Error-Enum, `canonicalize_paths`):
<https://docs.rs/trash/5.2.6/src/trash/lib.rs.html>
- Quelltext `windows.rs`, `freedesktop.rs`, `macos/mod.rs`, `Cargo.toml`, `README.md`,
`CHANGELOG.md`: aus dem Crate-Tarball
<https://static.crates.io/crates/trash/5.2.6/download>
(auf docs.rs **nicht** einsehbar — docs.rs baut auf Linux, `windows.rs` ist dort wegcfg-t)
- Repository (gepflegter Fork): <https://github.com/Byron/trash-rs>
- Offener Bug zum Netzlaufwerk-Fall: <https://github.com/Byron/trash-rs/issues/55>
- `IFileOperation::SetOperationFlags` (FOF_-/FOFX_-Flags, `FOF_WANTNUKEWARNING`):
<https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifileoperation-setoperationflags>
- `IFileOperationProgressSink::PostDeleteItem` (`psiNewlyCreated` ist NULL bei endgültiger Löschung):
<https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifileoperationprogresssink-postdeleteitem>
- Konstanten `FOF_NO_UI`, `TSF_DELETE_RECYCLE_IF_POSSIBLE` u. a.: generierte Bindings in
`windows 0.56.0`, `src/Windows/Win32/UI/Shell/mod.rs`
- FreeDesktop Trash Specification v1.0:
<https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html>