forked from 1b-agent/opencadstudio-upstream
fix(lock): other applications can open a drawing read-only while OCS edits it
On Windows the drawing was held with an exclusive byte lock, which also refuses reads, so AutoCAD / BricsCAD could not open it at all. The lease now reads through a handle that shares reads but refuses write opens, with a shared lock that refuses writes; the sidecar still keeps a second OCS session out. Unix keeps its advisory lock, which never blocked readers. Discussion #1416, issue #739 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0a3dab12e3
commit
ac2a1e1166
1 file changed
+36
-2
+36
-2
@@ -239,8 +239,42 @@ impl Drop for EditLease {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Open the drawing so other applications may still read it but not write it.
|
||||||
|
///
|
||||||
|
/// Windows: our handle only reads, and its share mode admits other readers
|
||||||
|
/// (and the atomic save's replace) while refusing any write open; the shared
|
||||||
|
/// byte lock below refuses writes through handles opened before ours. An
|
||||||
|
/// exclusive lock would also refuse reads, so another CAD program could not
|
||||||
|
/// even open the drawing read-only while it is being edited here.
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn open_drawing_for_lease(path: &Path) -> std::io::Result<File> {
|
||||||
|
use std::os::windows::fs::OpenOptionsExt;
|
||||||
|
use windows_sys::Win32::Storage::FileSystem::{FILE_SHARE_DELETE, FILE_SHARE_READ};
|
||||||
|
OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.share_mode(FILE_SHARE_READ | FILE_SHARE_DELETE)
|
||||||
|
.open(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
fn open_drawing_for_lease(path: &Path) -> std::io::Result<File> {
|
||||||
|
OpenOptions::new().read(true).write(true).open(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Windows takes the shared lock (writes refused, reads allowed); elsewhere the
|
||||||
|
/// lock is advisory and never stops a reader, so it stays exclusive.
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn lock_drawing(drawing: &File) -> Result<(), TryLockError> {
|
||||||
|
drawing.try_lock_shared()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
fn lock_drawing(drawing: &File) -> Result<(), TryLockError> {
|
||||||
|
drawing.try_lock()
|
||||||
|
}
|
||||||
|
|
||||||
fn acquire_drawing_lock(path: &Path) -> Result<(Option<File>, Option<String>), EditLeaseError> {
|
fn acquire_drawing_lock(path: &Path) -> Result<(Option<File>, Option<String>), EditLeaseError> {
|
||||||
let drawing = match OpenOptions::new().read(true).write(true).open(path) {
|
let drawing = match open_drawing_for_lease(path) {
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
Err(error) if error_is_lock_conflict(&error) => {
|
Err(error) if error_is_lock_conflict(&error) => {
|
||||||
return Err(EditLeaseError::Locked(format!(
|
return Err(EditLeaseError::Locked(format!(
|
||||||
@@ -255,7 +289,7 @@ fn acquire_drawing_lock(path: &Path) -> Result<(Option<File>, Option<String>), E
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match drawing.try_lock() {
|
match lock_drawing(&drawing) {
|
||||||
Ok(()) => Ok((Some(drawing), None)),
|
Ok(()) => Ok((Some(drawing), None)),
|
||||||
Err(TryLockError::WouldBlock) => Err(EditLeaseError::Locked(
|
Err(TryLockError::WouldBlock) => Err(EditLeaseError::Locked(
|
||||||
"Drawing is locked by another application.".to_string(),
|
"Drawing is locked by another application.".to_string(),
|
||||||
|
|||||||
Reference in new issue
Block a user