Compare commits

..
10 changed files with 114 additions and 71 deletions
Generated
+25 -27
View File
@@ -910,12 +910,12 @@ dependencies = [
[[package]]
name = "craft-fonts-build"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=6e03f65bf45181db77ba6229716e6624db9eb25b#6e03f65bf45181db77ba6229716e6624db9eb25b"
source = "git+https://github.com/storytold/craft-libs?rev=1b34eba41e62138fc2438d2734523b302840b45d#1b34eba41e62138fc2438d2734523b302840b45d"
[[package]]
name = "craft-i18n"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=6e03f65bf45181db77ba6229716e6624db9eb25b#6e03f65bf45181db77ba6229716e6624db9eb25b"
source = "git+https://github.com/storytold/craft-libs?rev=1b34eba41e62138fc2438d2734523b302840b45d#1b34eba41e62138fc2438d2734523b302840b45d"
dependencies = [
"fixed_decimal",
"icu_decimal",
@@ -938,7 +938,7 @@ dependencies = [
[[package]]
name = "craft-print"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=6e03f65bf45181db77ba6229716e6624db9eb25b#6e03f65bf45181db77ba6229716e6624db9eb25b"
source = "git+https://github.com/storytold/craft-libs?rev=1b34eba41e62138fc2438d2734523b302840b45d#1b34eba41e62138fc2438d2734523b302840b45d"
dependencies = [
"craft-winprint-gdi",
"log",
@@ -952,7 +952,7 @@ dependencies = [
[[package]]
name = "craft-ui"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=6e03f65bf45181db77ba6229716e6624db9eb25b#6e03f65bf45181db77ba6229716e6624db9eb25b"
source = "git+https://github.com/storytold/craft-libs?rev=1b34eba41e62138fc2438d2734523b302840b45d#1b34eba41e62138fc2438d2734523b302840b45d"
dependencies = [
"craft-fonts-build",
"craft-i18n",
@@ -972,7 +972,7 @@ dependencies = [
[[package]]
name = "craft-update"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=6e03f65bf45181db77ba6229716e6624db9eb25b#6e03f65bf45181db77ba6229716e6624db9eb25b"
source = "git+https://github.com/storytold/craft-libs?rev=1b34eba41e62138fc2438d2734523b302840b45d#1b34eba41e62138fc2438d2734523b302840b45d"
dependencies = [
"chrono",
"dunce",
@@ -982,7 +982,6 @@ dependencies = [
"image",
"log",
"plist",
"rustix 1.1.5",
"semver",
"serde",
"serde_json",
@@ -1019,7 +1018,7 @@ dependencies = [
[[package]]
name = "craft-window"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=6e03f65bf45181db77ba6229716e6624db9eb25b#6e03f65bf45181db77ba6229716e6624db9eb25b"
source = "git+https://github.com/storytold/craft-libs?rev=1b34eba41e62138fc2438d2734523b302840b45d#1b34eba41e62138fc2438d2734523b302840b45d"
dependencies = [
"egui",
"objc2 0.6.5",
@@ -2023,7 +2022,7 @@ dependencies = [
[[package]]
name = "gridcraft"
version = "0.19.1"
version = "0.19.0"
dependencies = [
"arboard",
"chrono",
@@ -2051,7 +2050,7 @@ dependencies = [
[[package]]
name = "gridcraft-calc"
version = "0.19.1"
version = "0.19.0"
dependencies = [
"gridcraft-core",
"gridcraft-formula",
@@ -2063,7 +2062,7 @@ dependencies = [
[[package]]
name = "gridcraft-chart"
version = "0.19.1"
version = "0.19.0"
dependencies = [
"gridcraft-calc",
"gridcraft-core",
@@ -2074,7 +2073,7 @@ dependencies = [
[[package]]
name = "gridcraft-cli"
version = "0.19.1"
version = "0.19.0"
dependencies = [
"gridcraft-engine",
"gridcraft-mcp",
@@ -2087,7 +2086,7 @@ dependencies = [
[[package]]
name = "gridcraft-core"
version = "0.19.1"
version = "0.19.0"
dependencies = [
"chrono",
"js-sys",
@@ -2098,7 +2097,7 @@ dependencies = [
[[package]]
name = "gridcraft-engine"
version = "0.19.1"
version = "0.19.0"
dependencies = [
"gridcraft-calc",
"gridcraft-chart",
@@ -2121,7 +2120,7 @@ dependencies = [
[[package]]
name = "gridcraft-formula"
version = "0.19.1"
version = "0.19.0"
dependencies = [
"gridcraft-core",
"serde",
@@ -2130,7 +2129,7 @@ dependencies = [
[[package]]
name = "gridcraft-functions"
version = "0.19.1"
version = "0.19.0"
dependencies = [
"gridcraft-core",
"regex",
@@ -2138,7 +2137,7 @@ dependencies = [
[[package]]
name = "gridcraft-math"
version = "0.19.1"
version = "0.19.0"
dependencies = [
"ratex-font",
"ratex-layout",
@@ -2148,7 +2147,7 @@ dependencies = [
[[package]]
name = "gridcraft-mcp"
version = "0.19.1"
version = "0.19.0"
dependencies = [
"gridcraft-engine",
"log",
@@ -2157,7 +2156,7 @@ dependencies = [
[[package]]
name = "gridcraft-model"
version = "0.19.1"
version = "0.19.0"
dependencies = [
"gridcraft-core",
"gridcraft-formula",
@@ -2170,18 +2169,18 @@ dependencies = [
[[package]]
name = "gridcraft-numfmt"
version = "0.19.1"
version = "0.19.0"
dependencies = [
"gridcraft-core",
]
[[package]]
name = "gridcraft-pdf"
version = "0.19.1"
version = "0.19.0"
[[package]]
name = "gridcraft-sandbox"
version = "0.19.1"
version = "0.19.0"
dependencies = [
"landlock",
"libc",
@@ -2192,7 +2191,7 @@ dependencies = [
[[package]]
name = "gridcraft-ui-egui"
version = "0.19.1"
version = "0.19.0"
dependencies = [
"craft-ui",
"craft-update",
@@ -2219,7 +2218,7 @@ dependencies = [
[[package]]
name = "gridcraft-web"
version = "0.19.1"
version = "0.19.0"
dependencies = [
"craft-web-api",
"craft-web-host",
@@ -2238,7 +2237,7 @@ dependencies = [
[[package]]
name = "gridcraft-xlsx"
version = "0.19.1"
version = "0.19.0"
dependencies = [
"aes",
"gridcraft-core",
@@ -6520,7 +6519,7 @@ dependencies = [
[[package]]
name = "winprint"
version = "0.2.1"
source = "git+https://github.com/storytold/winprint.rs?rev=bc06a7c48b768eb27a828c7986f6d2868b245ee0#bc06a7c48b768eb27a828c7986f6d2868b245ee0"
source = "git+https://github.com/storytold/winprint.rs?rev=51934db382f225454cf152a8657eb03789be63c4#51934db382f225454cf152a8657eb03789be63c4"
dependencies = [
"flate2",
"fmt-derive",
@@ -6535,7 +6534,6 @@ dependencies = [
"windows",
"windows-core",
"windows-implement",
"windows-numerics",
"xml",
]
@@ -6710,7 +6708,7 @@ checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
[[package]]
name = "xtask"
version = "0.19.1"
version = "0.19.0"
dependencies = [
"gridcraft-engine",
"serde_json",
+3 -3
View File
@@ -4,7 +4,7 @@ members = ["crates/*", "apps/*", "xtask"]
default-members = ["crates/*", "apps/gridcraft", "apps/gridcraft-cli", "xtask"]
[workspace.package]
version = "0.19.1"
version = "0.19.0"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.95"
@@ -29,7 +29,7 @@ gridcraft-math = { path = "crates/math" }
# Every craft-libs crate (craft-update and craft-window here, craft-ui in ui-egui, craft-print in
# the app) comes from one craft-libs revision.
# craft-update: app-specific pretend versions apply to startup and manual checks.
craft-update = { git = "https://github.com/storytold/craft-libs", rev = "6e03f65bf45181db77ba6229716e6624db9eb25b", version = "=0.1.0", features = ["egui"] }
craft-update = { git = "https://github.com/storytold/craft-libs", rev = "1b34eba41e62138fc2438d2734523b302840b45d", version = "=0.1.0", features = ["egui"] }
# foundation
thiserror = "2"
@@ -72,7 +72,7 @@ skrifa = { version = "0.44", default-features = false, features = ["std"] }
epaint_default_fonts = "0.37"
# One native window per workbook: the window list, Window menu titles and the immediate-viewport
# driver. Same craft-libs revision as craft-update above.
craft-window = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "6e03f65bf45181db77ba6229716e6624db9eb25b", default-features = false, features = ["egui"] }
craft-window = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "1b34eba41e62138fc2438d2734523b302840b45d", default-features = false, features = ["egui"] }
[workspace.lints.rust]
unsafe_code = "forbid"
+1 -1
View File
@@ -35,7 +35,7 @@ winit = { version = "0.30", default-features = false, features = ["x11", "waylan
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
# Same craft-libs revision as the workspace's craft-update and craft-window (see the root
# Cargo.toml).
craft-print = { git = "https://github.com/storytold/craft-libs", rev = "6e03f65bf45181db77ba6229716e6624db9eb25b" }
craft-print = { git = "https://github.com/storytold/craft-libs", rev = "1b34eba41e62138fc2438d2734523b302840b45d" }
craft-update = { workspace = true }
rfd = { workspace = true }
arboard = { workspace = true, features = ["image-data"] }
+43 -4
View File
@@ -924,17 +924,56 @@ fn script_sandbox_check(path: &str) -> Result<()> {
#[cfg(not(target_arch = "wasm32"))]
const MAX_READ_BYTES: u64 = 2 * 1024 * 1024 * 1024;
/// `O_NONBLOCK` as the platform defines it (std offers no constant and the crate takes no libc
/// dependency); `None` on a Unix not listed, where the pathname check alone applies.
#[cfg(all(unix, not(target_arch = "wasm32")))]
const O_NONBLOCK: Option<i32> =
if cfg!(any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "dragonfly"))
{
Some(0x4)
} else if cfg!(all(
any(target_os = "linux", target_os = "android"),
any(target_arch = "mips", target_arch = "mips64", target_arch = "mips32r6", target_arch = "mips64r6")
)) {
Some(0x80)
} else if cfg!(all(any(target_os = "linux", target_os = "android"), any(target_arch = "sparc", target_arch = "sparc64"))) {
Some(0x4000)
} else if cfg!(any(target_os = "linux", target_os = "android")) {
Some(0o4000)
} else {
None
};
/// Opens `path` for reading without blocking on a FIFO or device that has no writer.
#[cfg(not(target_arch = "wasm32"))]
fn open_nonblocking(path: &std::path::Path) -> std::io::Result<std::fs::File> {
let mut opts = std::fs::OpenOptions::new();
opts.read(true);
#[cfg(unix)]
if let Some(flag) = O_NONBLOCK {
use std::os::unix::fs::OpenOptionsExt;
opts.custom_flags(flag);
}
opts.open(path)
}
#[cfg(not(target_arch = "wasm32"))]
pub fn read_file(path: &str) -> Result<Vec<u8>> {
use std::io::Read;
script_sandbox_check(path)?;
// Checked before opening: opening a FIFO nobody writes to blocks the calling (GUI) thread.
// shortcut: a FIFO swapped in between this check and the open still blocks; close that with
// O_NONBLOCK when the crate takes a libc dependency.
// Checked before opening: opening a FIFO nobody writes to blocks the calling (GUI) thread. The
// open itself is non-blocking where the platform's flag is known, so a FIFO swapped in after
// this check cannot block it either; the opened handle is checked again below.
if !std::fs::metadata(path).map_err(|e| EngineError::Other(format!("{path}: {e}")))?.is_file() {
return Err(EngineError::Other(format!("{path}: only regular files can be opened")));
}
let f = std::fs::File::open(path).map_err(|e| EngineError::Other(format!("{path}: {e}")))?;
let f = match open_nonblocking(std::path::Path::new(path)) {
Ok(f) => f,
// A conflicting lease (Samba, NFS) makes a non-blocking open of a regular file wait to be retried; the
// path was just checked to be a regular file, so open it the plain way, as before.
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => std::fs::File::open(path).map_err(|e| EngineError::Other(format!("{path}: {e}")))?,
Err(e) => return Err(EngineError::Other(format!("{path}: {e}"))),
};
// A symlink to an endless device reports length 0 but streams forever, so metadata
// alone never bounds the read: require a real file and cap the bytes actually taken.
if !f.metadata().map_err(|e| EngineError::Other(format!("{path}: {e}")))?.is_file() {
+41
View File
@@ -810,3 +810,44 @@ fn reading_a_fifo_is_refused_without_blocking() {
assert!(err.contains("only regular files"), "{err}");
let _ = std::fs::remove_dir_all(&dir);
}
/// Opening a FIFO nobody writes to returns at once (it is not a regular file), where a plain open
/// blocks the calling thread for good.
#[cfg(unix)]
#[test]
fn a_fifo_without_a_writer_does_not_block_the_open() {
if O_NONBLOCK.is_none() {
return;
}
/// Removes the folder when the test ends, however it ends.
struct Cleanup(std::path::PathBuf);
impl Drop for Cleanup {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
// Created exclusively (a fresh name per try), so no other process shares it.
let dir = (0..100)
.map(|n| {
std::env::temp_dir().join(format!(
"gridcraft-fifo-{}-{n}-{}",
std::process::id(),
std::time::UNIX_EPOCH.elapsed().map_or(0, |t| t.as_nanos())
))
})
.find(|d| std::fs::create_dir(d).is_ok())
.expect("a fresh temporary folder");
let _cleanup = Cleanup(dir.clone());
let fifo = dir.join("pipe");
if !std::process::Command::new("mkfifo").arg(&fifo).status().is_ok_and(|s| s.success()) {
return; // no mkfifo here
}
let (tx, rx) = std::sync::mpsc::channel();
let path = fifo.clone();
std::thread::spawn(move || {
let _ = tx.send(open_nonblocking(&path).and_then(|f| f.metadata()).map(|m| m.is_file()));
});
let opened = rx.recv_timeout(std::time::Duration::from_secs(10)).expect("the open blocked");
assert!(!opened.unwrap());
assert!(read_file(&fifo.to_string_lossy()).is_err());
}
+1 -1
View File
@@ -11,7 +11,7 @@ workspace = true
[dependencies]
# The shared capture slot (`craft_ui::capture`) comes from the one craft-libs revision every
# craft-libs dependency uses (see the root Cargo.toml).
craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "6e03f65bf45181db77ba6229716e6624db9eb25b" }
craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "1b34eba41e62138fc2438d2734523b302840b45d" }
gridcraft-engine = { workspace = true }
gridcraft-chart = { workspace = true }
# `monochrome_emoji_fonts` restores the Noto Emoji and emoji-icon faces that 0.37 moved behind
-27
View File
@@ -1,27 +0,0 @@
//! Release and source Flatpaks must allow release checks and host update helpers.
fn finish_args(manifest: &str) -> Vec<&str> {
manifest
.split("finish-args:")
.nth(1)
.unwrap_or_default()
.lines()
.take_while(|line| line.is_empty() || line.starts_with(' ') || line.starts_with('#'))
.filter_map(|line| line.trim().strip_prefix("- "))
.map(|arg| arg.trim_matches(['\'', '"']))
.collect()
}
#[test]
fn flatpak_update_permissions() {
let manifests = [
include_str!("../../../packaging/linux/flatpak/ai.storyteller.gridcraft.bundle.yml"),
include_str!("../../../packaging/linux/flatpak/ai.storyteller.gridcraft.yml"),
];
let args = manifests.map(finish_args);
assert_eq!(args[0], args[1], "source and release permissions must agree");
for permissions in args {
for required in ["--share=network", "--talk-name=org.freedesktop.Flatpak"] {
assert!(permissions.contains(&required), "missing update permission: {required}");
}
}
}
@@ -17,8 +17,6 @@ runtime-version: '25.08'
sdk: org.freedesktop.Sdk
command: gridcraft
finish-args:
# The shared updater inspects and updates this installation via flatpak-spawn --host.
- --talk-name=org.freedesktop.Flatpak
- --filesystem=xdg-data/ArtCraft/versions:ro
# Display: Wayland first; X11 only when no Wayland compositor is running.
- --socket=wayland
@@ -18,8 +18,6 @@ sdk-extensions:
- org.freedesktop.Sdk.Extension.rust-stable
command: gridcraft
finish-args:
# The shared updater inspects and updates this installation via flatpak-spawn --host.
- --talk-name=org.freedesktop.Flatpak
- --filesystem=xdg-data/ArtCraft/versions:ro
# Display: Wayland first; X11 only when no Wayland compositor is running.
- --socket=wayland
-4
View File
@@ -1,6 +1,2 @@
sonar.projectKey=storytold_gridcraft
sonar.organization=storytold
# The region table is one record per locale with the same field names and many shared values
# (month names, formats); that repetition is data, not copy-pasted logic.
sonar.cpd.exclusions=crates/core/src/region.rs