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

  1. 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.
  2. Handle the None basename case explicitly with a user-visible message instead of an anyhow bail.
  3. If remote/portal locations must be supported, fall back to deriving a name from `file.uri()` or the document title.
  4. 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

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


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)