Say when something is downloading, and where it goes

The webview saves the file perfectly well and mentions it to nobody,
which makes a download indistinguishable from a click that did nothing.
Each one is now announced in the corner as it saves, and offers to show
the finished file in the Finder. The folder is settable and defaults to
the system's Downloads.

Two gaps in the API shape this. There is no progress - DownloadEvent
reports a start and a finish and nothing between - so the destination
file is polled as it grows and the bytes written are shown; the indicator
spins rather than fills, because there is no total to divide by. And on
macOS the finish always reports no path at all, so the destination
assigned at request time is remembered against the URL and read back at
the end.

A name already taken gets "(2)" appended. Silently overwriting is the
last thing anyone wants from a download they were not told about.
This commit is contained in:
2026-09-02 10:42:45 +02:00
parent e1ac3d7509
commit 04b731cf37
20 changed files with 750 additions and 4 deletions
+54
View File
@@ -686,6 +686,7 @@ pub fn create(
let load_handle = handle.clone();
let load_id = id.clone();
let dl_handle = handle.clone();
let builder = WebviewBuilder::new(label_for(&id), WebviewUrl::External(url))
.user_agent(&app.ua())
@@ -716,6 +717,59 @@ pub fn create(
}
NewWindowResponse::Deny
})
// WebKit saves the file happily on its own; what it never does is
// mention it. Redirected to the chosen folder, and announced.
.on_download(move |_wv, event| {
use tauri::webview::DownloadEvent;
let state = dl_handle.state::<crate::commands::AppState>();
match event {
DownloadEvent::Requested { url, destination } => {
let dir = crate::downloads::folder(&dl_handle);
let _ = std::fs::create_dir_all(&dir);
let name = crate::downloads::name_from(&url, destination);
let path = crate::downloads::unique(&dir, &name);
let id = crate::downloads::next_id();
state
.download_paths
.lock()
.unwrap()
.insert(url.to_string(), (id, path.clone()));
let _ = dl_handle.emit(
"download-started",
crate::downloads::DownloadStarted {
id,
name,
path: path.to_string_lossy().into_owned(),
},
);
crate::downloads::watch(&dl_handle, id, path.clone());
*destination = path;
}
DownloadEvent::Finished { url, success, .. } => {
// The path is never reported back on macOS, so it comes
// from what was assigned when the download was requested.
let found = state.download_paths.lock().unwrap().remove(&url.to_string());
if let Some((id, path)) = found {
state.finished_downloads.lock().unwrap().insert(id);
let _ = dl_handle.emit(
"download-finished",
crate::downloads::DownloadFinished {
id,
success,
path: path.to_string_lossy().into_owned(),
},
);
}
}
_ => {}
}
true
})
// The script carries a snapshot of the hidden list from when the view
// was built, so anything chosen since would come back on reload. This
// re-asserts the real list on every navigation.