flxzt/rnote · error · anyhow::Error
Could not retrieve basename for file
Error message
Could not retrieve basename for file: `{file:?}`. What it means
Thrown in `save_document_to_file` when `gio::File::basename()` returns None, i.e. the GFile has no extractable base name. This happens when the file URI cannot be parsed into a concrete filesystem path (e.g. a non-file URI scheme or a failure querying the file info). Before failing, the save-in-progress flag is reset so the UI does not hang in a saving state.
Solutions
- Ensure the save dialog returns a local filesystem URI/path (Gio.File::new_for_path or a real chosen file), not a scheme-less or remote URI.
- Handle the None basename case explicitly with a user-visible message instead of an anyhow bail.
- If remote/portal locations must be supported, fall back to deriving a name from `file.uri()` or the document title.
- Verify the storage backend (gvfs mount, network share) supports basic attribute queries.
Example fix
// before
let basename = file.basename().ok_or_else(|| {
self.set_save_in_progress(false);
anyhow::anyhow!("Could not retrieve basename for file: `{file:?}`.")
})?;
// after
let basename = file.basename().or_else(|| {
file.uri()
.split('/')
.last()
.and_then(|s| if s.is_empty() { None } else { Some(glib::filename_display_basename(&file.path()?)) })
}).ok_or_else(|| {
self.set_save_in_progress(false);
anyhow::anyhow!("Could not retrieve basename for file: `{file:?}`.")
})?; Defensive patterns
Strategy: validation
Validate before calling
let is_regular = file.query_file_type(gio::FileQueryInfoFlags::NONE, gio::Cancellable::NONE) == gio::FileType::Regular;
let path_ok = file.path().is_some();
if !(is_regular && path_ok) { bail!("file has no usable local path/basename"); } Type guard
fn has_basename(file: &gio::File) -> bool {
file.path().map(|p| p.file_name().is_some()).unwrap_or(false)
} Prevention
- Always obtain the save target from a FileChooser returning local paths
- Reject remote/unsupported URI schemes before starting the save flow
- Keep the UI save-in-progress flag in sync in both success and error paths
When it happens
Trigger: Calling `save_document_to_file` with a GFile whose `path()` resolved but whose `basename()` query fails or returns None — typically a file backed by a non-filesystem URI (e.g. http://, trash://, recent://) or a backend that does not support the `standard::name` attribute.
Common situations: Saving via a portal/remote location or mount that exposes URIs without local paths; a stale or cancelled file chooser handle; gvfs-backed URIs where attribute query is unavailable.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- Supplied target file
- Could not open file ' ', file path is None.
- Failed to reload file from disk, no file path saved.
- Could not get a path for file
- Can't create-replace file that has no path.
AI-assisted analysis of flxzt/rnote@bbc5354502 (2026-09-08).
Data as JSON: /api/errors/3fea549832ac9bea.
Report an issue: GitHub.
Appendix: source
Thrown at crates/rnote-ui/src/canvas/imexport.rs:233
/// - `Ok(false)` if a save was already in progress (and thus this function didn't do anything)
/// - `Err(e)` when saving failed in any way
#[tracing::instrument(skip_all, fields(path = format!("{:?}", file.path())))]
pub(crate) async fn save_document_to_file(&self, file: &gio::File) -> anyhow::Result<bool> {
// skip saving when it is already in progress
if self.save_in_progress() {
debug!("Returning early, saving file is already in progress");
return Ok(false);
}
self.set_save_in_progress(true);
debug!("Saving file is now in progress");
let filepath = file.path().ok_or_else(|| {
self.set_save_in_progress(false);
anyhow::anyhow!("Could not get a path for file: `{file:?}`.")
})?;
let basename = file.basename().ok_or_else(|| {
self.set_save_in_progress(false);
anyhow::anyhow!("Could not retrieve basename for file: `{file:?}`.")
})?;
let rnote_bytes_receiver = self
.engine_ref()
.save_as_rnote_bytes(basename.to_string_lossy().to_string());
let mut skip_set_output_file = false;
if let Some(output_filepath) = self.output_file().and_then(|f| f.path())
&& crate::utils::paths_abs_eq(output_filepath, &filepath).unwrap_or(false)
{
skip_set_output_file = true;
}
self.dismiss_output_file_modified_toast();
let file_write_operation = async {
let bytes = rnote_bytes_receiver.await??;
// The `output_file_expect_write` should theoretically be reset to `false` by the file watcher later.
self.set_output_file_expect_write(true);View on GitHub (pinned to bbc5354502)