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

27 KiB
Raw Blame History

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:

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:

[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" (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:

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:

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:

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:

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:

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:

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