flxzt/rnote · error · anyhow::Error

Can't create-replace file that has no path.

Error message

Can't create-replace file that has no path.

What it means

This error is thrown by create_replace_file_future when the given gio::File has no filesystem path. gio::File can represent non-native locations (e.g. GVfs/remote URIs like trash://, sftp://, http://) for which path() returns None. Since the function writes bytes with async_fs::OpenOptions, which requires a native path, it fails fast instead of attempting a doomed write.

Solutions

  1. Check file.path() (or file.query_info for G_FILE_ATTRIBUTE_LOCAL_PATH) is Some before calling create_replace_file_future, and surface a user-facing message for non-local files.
  2. Use file.has_uri_scheme("file") to verify the File is native; for remote files, copy them to a local temp file (file.copy) and operate on that instead.
  3. If saving from Trash, restore the file to a real location first, then create-replace at the restored path.
  4. Normalize command-line/DBus-provided arguments with gio::File::new_for_path after validating the input is an absolute filesystem path rather than new_for_commandline_arg/URI.

Example fix

// before
let file = gio::File::for_uri("trash:///note.rnote");
create_replace_file_future(bytes, &file).await?;
// after
let file = gio::File::for_uri("trash:///note.rnote");
if file.path().is_none() {
    anyhow::bail!("cannot save: {} is not a local file", file.uri());
}
create_replace_file_future(bytes, &file).await?;
Defensive patterns

Strategy: validation

Validate before calling

fn ensure_local_file(file: &gio::File) -> anyhow::Result<std::path::PathBuf> {
    file.path().ok_or_else(|| anyhow::anyhow!(
        "'{}' is not a local file and cannot be written directly", file.uri()
    ))
}

Type guard

fn is_writable_local_file(file: &gio::File) -> bool {
    file.path().map(|p| p.is_file() || !p.exists()).unwrap_or(false)
}

Try / catch

match create_replace_file_future(bytes, &file).await {
    Ok(()) => {},
    Err(e) if e.to_string().contains("no path") => {
        // non-native gio::File: offer 'Save As' to a local location
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling create_replace_file_future with a gio::File constructed from a non-file URI (trash:///, network://, sftp://, http://), or from a File whose path has been unmounted/deleted, or a File created via gio::File::new_for_commandline_arg with a non-path argument; any call where file.path() returns None.

Common situations: Saving a document from the Trash or a recently-used remote location, drag-and-drop of a file from a remote/GVfs share into the app, launching the app with a URI instead of a local path (e.g. via DBus activation or command line), or the target file's mount having been disconnected before save.

Related errors


AI-assisted analysis of flxzt/rnote@bbc5354502 (2026-09-08). Data as JSON: /api/errors/7a6a48c059c47193. Report an issue: GitHub.

Appendix: source

Thrown at crates/rnote-ui/src/utils.rs:34

pub(crate) const FILE_DUP_SUFFIX_DELIM_REGEX: &str = r"\s-\s";

/// An asynchronous adaptation of the [`rnote_engine::utils::atomic_save_to_file`] function.
pub(crate) async fn atomic_save_to_file_future<Q>(filepath: Q, bytes: Vec<u8>) -> anyhow::Result<()>
where
    Q: AsRef<std::path::Path>,
{
    let filepath = filepath.as_ref().to_path_buf();

    blocking::unblock(move || rnote_engine::utils::atomic_save_to_file(filepath, bytes)).await
}

/// Create a new file or replace if it already exists, asynchronously.
pub(crate) async fn create_replace_file_future(
    bytes: Vec<u8>,
    file: &gio::File,
) -> anyhow::Result<()> {
    let Some(file_path) = file.path() else {
        return Err(anyhow::anyhow!(
            "Can't create-replace file that has no path."
        ));
    };
    let mut write_file = async_fs::OpenOptions::new()
        .create(true)
        .truncate(true)
        .write(true)
        .open(&file_path)
        .await
        .context(format!(
            "Failed to create/open/truncate file for path '{}'",
            file_path.display()
        ))?;
    write_file.write_all(&bytes).await.context(format!(
        "Failed to write bytes to file with path '{}'",
        file_path.display()
    ))?;
    write_file.sync_all().await.context(format!(

View on GitHub (pinned to bbc5354502)