M4.36: Background Save and Export write a snapshot off the UI thread with status bar progress

- With Preferences > File Handling > Enable Background Save / Export on, File >
  Save, Save As, Save a Copy and exports encode and write a snapshot of the
  document on a worker thread; the call returns at once ({path, background}).
- The status bar shows a spinner and what is being written; ui.inspect lists
  the running jobs under `background`.
- The document takes the path and counts as saved as of the snapshot once the
  file is written: edits made meanwhile keep it modified.
- Quit and Close wait for running saves; the Save Changes dialog saves at once.
- The host gives a thread-safe writer (Services::write_shared, atomic on the
  desktop); without one (web, tests) everything runs at once as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bflatastic
2026-10-05 12:27:06 +07:00
co-authored by Claude Opus 5.5
parent 65cdc45ed9
commit db0a7ffd8b
14 changed files with 590 additions and 94 deletions
+13 -6
View File
@@ -117,6 +117,18 @@ impl Default for SaveOptions {
}
impl SaveOptions {
/// Can a file be written this way (a version [`save_with`] writes, compressed only for readers
/// that open compressed files)?
pub fn check(&self) -> Result<(), FormatError> {
if !(MIN_VERSION..=VERSION).contains(&self.version) {
return Err(FormatError::BadVersion(self.version));
}
if self.compress && self.version < COMPRESSED_SINCE {
return Err(FormatError::CompressedTooOld(self.version));
}
Ok(())
}
/// The defaults for saving `doc` to disk: pretty when small (see [`PRETTY_MAX_OBJECTS`]).
pub fn for_doc(doc: &Document) -> Self {
let objects: usize = doc.layers.iter().map(|l| l.count()).sum();
@@ -142,12 +154,7 @@ pub fn save_file(doc: &Document) -> Vec<u8> {
/// Serialize a document as `o` says: the version, compression, layout and preview. Editing-mode
/// working copies and images nothing uses are left out.
pub fn save_with(doc: &Document, o: &SaveOptions) -> Result<Vec<u8>, FormatError> {
if !(MIN_VERSION..=VERSION).contains(&o.version) {
return Err(FormatError::BadVersion(o.version));
}
if o.compress && o.version < COMPRESSED_SINCE {
return Err(FormatError::CompressedTooOld(o.version));
}
o.check()?;
let pretty = o.pretty && !o.compress;
let mut d = doc.without_edit_modes().into_owned();
let linked = d.linked_only_images();