{"record":{"id":"7a6a48c059c47193","repo":"flxzt/rnote","slug":"can-t-create-replace-file-that-has-no-path","errorCode":null,"errorMessage":"Can't create-replace file that has no path.","messagePattern":"Can't create-replace file that has no path\\.","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/rnote-ui/src/utils.rs","lineNumber":34,"sourceCode":"pub(crate) const FILE_DUP_SUFFIX_DELIM_REGEX: &str = r\"\\s-\\s\";\n\n/// An asynchronous adaptation of the [`rnote_engine::utils::atomic_save_to_file`] function.\npub(crate) async fn atomic_save_to_file_future<Q>(filepath: Q, bytes: Vec<u8>) -> anyhow::Result<()>\nwhere\n    Q: AsRef<std::path::Path>,\n{\n    let filepath = filepath.as_ref().to_path_buf();\n\n    blocking::unblock(move || rnote_engine::utils::atomic_save_to_file(filepath, bytes)).await\n}\n\n/// Create a new file or replace if it already exists, asynchronously.\npub(crate) async fn create_replace_file_future(\n    bytes: Vec<u8>,\n    file: &gio::File,\n) -> anyhow::Result<()> {\n    let Some(file_path) = file.path() else {\n        return Err(anyhow::anyhow!(\n            \"Can't create-replace file that has no path.\"\n        ));\n    };\n    let mut write_file = async_fs::OpenOptions::new()\n        .create(true)\n        .truncate(true)\n        .write(true)\n        .open(&file_path)\n        .await\n        .context(format!(\n            \"Failed to create/open/truncate file for path '{}'\",\n            file_path.display()\n        ))?;\n    write_file.write_all(&bytes).await.context(format!(\n        \"Failed to write bytes to file with path '{}'\",\n        file_path.display()\n    ))?;\n    write_file.sync_all().await.context(format!(","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/flxzt/rnote/blob/bbc5354502ba2fc83eec2670b535348825e679a6/crates/rnote-ui/src/utils.rs#L16-L52","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","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.","If saving from Trash, restore the file to a real location first, then create-replace at the restored path.","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."],"exampleFix":"// before\nlet file = gio::File::for_uri(\"trash:///note.rnote\");\ncreate_replace_file_future(bytes, &file).await?;\n// after\nlet file = gio::File::for_uri(\"trash:///note.rnote\");\nif file.path().is_none() {\n    anyhow::bail!(\"cannot save: {} is not a local file\", file.uri());\n}\ncreate_replace_file_future(bytes, &file).await?;","handlingStrategy":"validation","validationCode":"fn ensure_local_file(file: &gio::File) -> anyhow::Result<std::path::PathBuf> {\n    file.path().ok_or_else(|| anyhow::anyhow!(\n        \"'{}' is not a local file and cannot be written directly\", file.uri()\n    ))\n}","typeGuard":"fn is_writable_local_file(file: &gio::File) -> bool {\n    file.path().map(|p| p.is_file() || !p.exists()).unwrap_or(false)\n}","tryCatchPattern":"match create_replace_file_future(bytes, &file).await {\n    Ok(()) => {},\n    Err(e) if e.to_string().contains(\"no path\") => {\n        // non-native gio::File: offer 'Save As' to a local location\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Only construct gio::File via new_for_path for local saves; treat URIs as needing a copy-to-local step first","Check file.has_uri_scheme(\"file\") early in the open/load flow and warn the user before edits are made","Re-check file.path() right before save in case the mount was unmounted in between","Offer 'Save As...' as fallback whenever the source document has no native path"],"tags":["gio","filesystem","gio-file","no-path","save"],"backgroundTag":"null-argument","analyzedSha":"bbc5354502ba2fc83eec2670b535348825e679a6","analyzedAt":"2026-09-08T13:20:33.747Z","contentChangedAt":"2026-09-08T13:20:33.747Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}