forked from 1b-agent/opencadstudio-upstream
fix: final pre-PR review sweep
The final holistic + quality audit (three reviewers) returned GO with four majors; all fixed. - Start-page recents were the last unguarded writer of the open slot on web: clicking one under the open overlay clobbered the in-flight open and silently lost its bytes. Refused with the same in-progress notice the picker uses. - The web quit-after-save continuation stalled: a Quit pending now survives the tab-save AND continues — offering to save the next dirty tab or finishing the quit, mirroring native. - Data-extraction's multi-file pick is capped (16): every picked file becomes a full parsed document held for the session, and one giant pick could exhaust the wasm heap. - The xref registry tests raced each other on the process-global registry (reproduced 3/10 at high thread counts — CI roulette); they now hold a module-level guard. Minors: the collision warning is cataloged in all 21 locales; the docs' parallelism detail header no longer contradicts its own table; the drop-filter and name-hash helpers gained inline tests (their only prior coverage was a verbatim copy in the red-team suites).
This commit is contained in:
1 parent
b635d56c00
commit
f70d3fda44
29 files changed
+101
-1
No files matched your search
@@ -76,7 +76,7 @@ shared: native renders the same modals too, so the desktop is effectively
|
||||
single-window now. A modal's backdrop dims and blocks clicks but does not
|
||||
dismiss; the ✕ button closes it.
|
||||
|
||||
### Parallelism — web: single-threaded
|
||||
### Parallelism — web: main thread plus one parse/serialize worker
|
||||
`wasm32` has no threads without SharedArrayBuffer (which needs COOP/COEP
|
||||
headers GitHub Pages can't set). `crate::par::prelude` re-exports `rayon` on
|
||||
native and sequential `std` iterators on wasm, so the same call sites run in
|
||||
|
||||
@@ -3584,6 +3584,7 @@ view =
|
||||
.opening-ocs-discussions-for-help-and-questions = يجري فتح مناقشات OCS للمساعدة والأسئلة...
|
||||
.opening-ocs-web = يجري فتح OCS Web...
|
||||
.opening-the-command-reference = فتح مرجع الأوامر...
|
||||
.already-loaded-different-contents-kept = "__ocs_fmt_0__" محمّل بالفعل بمحتوى مختلف؛ تم الاحتفاظ بالملف الموجود.
|
||||
.opening-patreon-page = يجري فتح صفحة Patreon...
|
||||
.opening-feedback-page = يجري فتح صفحة الملاحظات...
|
||||
.opening-release-notes = يجري فتح ملاحظات الإصدار...
|
||||
|
||||
@@ -3571,6 +3571,7 @@ view =
|
||||
.opening-ocs-discussions-for-help-and-questions = Отваряне на OCS дискусии за помощ и въпроси...
|
||||
.opening-ocs-web = Отваряне на OCS уебсайта...
|
||||
.opening-the-command-reference = Отваряне на справочника за команди...
|
||||
.already-loaded-different-contents-kept = "__ocs_fmt_0__" вече е зареден с различно съдържание; съществуващият файл е запазен.
|
||||
.opening-patreon-page = Отваряне на страницата в Patreon...
|
||||
.opening-feedback-page = Отваряне на страницата за обратна връзка...
|
||||
.opening-release-notes = Отваряне на бележките по версията...
|
||||
|
||||
@@ -3569,6 +3569,7 @@ view =
|
||||
.opening-ocs-discussions-for-help-and-questions = Otevírání diskuzí OCS pro nápovědu a dotazy...
|
||||
.opening-ocs-web = Otevírání webu OCS...
|
||||
.opening-the-command-reference = Otevírání referenční příručky příkazů...
|
||||
.already-loaded-different-contents-kept = "__ocs_fmt_0__" je již načten s jiným obsahem; existující soubor byl zachován.
|
||||
.opening-patreon-page = Otevírání stránky Patreon...
|
||||
.opening-feedback-page = Otevírá se stránka zpětné vazby...
|
||||
.opening-release-notes = Otevírání poznámek k vydání...
|
||||
|
||||
@@ -3570,6 +3570,7 @@ view =
|
||||
.opening-ocs-discussions-for-help-and-questions = Eröffnung OCS Diskussionen für Hilfe und Fragen...
|
||||
.opening-ocs-web = Öffnen OCS Web...
|
||||
.opening-the-command-reference = Öffnen der Befehlsreferenz...
|
||||
.already-loaded-different-contents-kept = "__ocs_fmt_0__" ist bereits mit anderem Inhalt geladen; die vorhandene Datei wurde behalten.
|
||||
.opening-patreon-page = Eröffnungsseite Patreon...
|
||||
.opening-feedback-page = Feedbackseite öffnen...
|
||||
.opening-release-notes = Opening Release Notes...
|
||||
|
||||
@@ -3566,6 +3566,7 @@ view =
|
||||
.opening-ocs-discussions-for-help-and-questions = Άνοιγμα των συζητήσεων OCS για βοήθεια και ερωτήσεις...
|
||||
.opening-ocs-web = Άνοιγμα του OCS Web...
|
||||
.opening-the-command-reference = Άνοιγμα της αναφοράς εντολών...
|
||||
.already-loaded-different-contents-kept = Το "__ocs_fmt_0__" είναι ήδη φορτωμένο με διαφορετικό περιεχόμενο· διατηρήθηκε το υπάρχον αρχείο.
|
||||
.opening-patreon-page = Άνοιγμα της σελίδας Patreon...
|
||||
.opening-feedback-page = Άνοιγμα της σελίδας σχολίων...
|
||||
.opening-release-notes = Άνοιγμα των σημειώσεων έκδοσης...
|
||||
|
||||
@@ -3567,6 +3567,7 @@ view =
|
||||
.opening-ocs-discussions-for-help-and-questions = Opening OCS Discussions for help and questions...
|
||||
.opening-ocs-web = Opening OCS Web...
|
||||
.opening-the-command-reference = Opening the command reference...
|
||||
.already-loaded-different-contents-kept = "__ocs_fmt_0__" is already loaded with different contents; the existing file was kept.
|
||||
.opening-patreon-page = Opening Patreon page...
|
||||
.opening-feedback-page = Opening feedback page...
|
||||
.opening-release-notes = Opening release notes...
|
||||
|
||||
@@ -3572,6 +3572,7 @@ view =
|
||||
.opening-ocs-discussions-for-help-and-questions = Abriendo Debates de OCS para obtener ayuda y hacer preguntas...
|
||||
.opening-ocs-web = Abriendo OCS Web...
|
||||
.opening-the-command-reference = Abriendo la referencia de comandos...
|
||||
.already-loaded-different-contents-kept = "__ocs_fmt_0__" ya está cargado con contenido diferente; se conservó el archivo existente.
|
||||
.opening-patreon-page = Abriendo la página de Patreon...
|
||||
.opening-feedback-page = Abriendo la página de comentarios...
|
||||
.opening-release-notes = Abriendo las notas de la versión...
|
||||
|
||||
@@ -3571,6 +3571,7 @@ view =
|
||||
.opening-ocs-discussions-for-help-and-questions = OCS-keskustelujen avaaminen apua ja kysymyksiä varten...
|
||||
.opening-ocs-web = Avataan OCS Web...
|
||||
.opening-the-command-reference = Avataan komentoviittausta...
|
||||
.already-loaded-different-contents-kept = "__ocs_fmt_0__" on jo ladattu eri sisällöllä; olemassa oleva tiedosto säilytettiin.
|
||||
.opening-patreon-page = Avataan Patreon-sivua...
|
||||
.opening-feedback-page = Palautesivua avataan...
|
||||
.opening-release-notes = Avataan julkaisutietoja...
|
||||
|
||||
@@ -3572,6 +3572,7 @@ view =
|
||||
.opening-ocs-discussions-for-help-and-questions = Ouverture OCS Discussions pour l'aide et les questions...
|
||||
.opening-ocs-web = Ouverture du site Web OCS...
|
||||
.opening-the-command-reference = Ouverture de la référence des commandes...
|
||||
.already-loaded-different-contents-kept = "__ocs_fmt_0__" est déjà chargé avec un contenu différent ; le fichier existant a été conservé.
|
||||
.opening-patreon-page = Ouverture de la page Patreon...
|
||||
.opening-feedback-page = Ouverture de la page de rétroaction...
|
||||
.opening-release-notes = Ouverture des notes de sortie...
|
||||
|
||||
@@ -3560,6 +3560,7 @@ view =
|
||||
.opening-ocs-discussions-for-help-and-questions = OCS मदद और प्रश्नों के लिए चर्चा खोलना।
|
||||
.opening-ocs-web = OCS वेब खोलना ...
|
||||
.opening-the-command-reference = कमांड संदर्भ खोला जा रहा है...
|
||||
.already-loaded-different-contents-kept = "__ocs_fmt_0__" अलग सामग्री के साथ पहले से लोड है; मौजूदा फ़ाइल रखी गई।
|
||||
.opening-patreon-page = Patreon पृष्ठ खोला जा रहा है…
|
||||
.opening-feedback-page = प्रतिक्रिया पृष्ठ खोलना
|
||||
.opening-release-notes = रिलीज नोट्स खोलना ...
|
||||
|
||||
@@ -3571,6 +3571,7 @@ view =
|
||||
.opening-ocs-discussions-for-help-and-questions = A OCS beszélgetések megnyitása segítségért és kérdésekért...
|
||||
.opening-ocs-web = A OCS web megnyitása...
|
||||
.opening-the-command-reference = A parancsreferencia megnyitása...
|
||||
.already-loaded-different-contents-kept = A(z) "__ocs_fmt_0__" már betöltve eltérő tartalommal; a meglévő fájl megőrizve.
|
||||
.opening-patreon-page = Patreon oldal megnyitása...
|
||||
.opening-feedback-page = Visszajelzési oldal megnyitása...
|
||||
.opening-release-notes = Kiadási megjegyzések megnyitása...
|
||||
|
||||
@@ -3571,6 +3571,7 @@ view =
|
||||
.opening-ocs-discussions-for-help-and-questions = Apertura delle discussioni OCS per assistenza e domande...
|
||||
.opening-ocs-web = Apertura del Web OCS...
|
||||
.opening-the-command-reference = Apertura del riferimento dei comandi...
|
||||
.already-loaded-different-contents-kept = "__ocs_fmt_0__" è già caricato con contenuti diversi; il file esistente è stato mantenuto.
|
||||
.opening-patreon-page = Apertura della pagina Patreon...
|
||||
.opening-feedback-page = Apertura della pagina di feedback...
|
||||
.opening-release-notes = Note sulla versione di apertura...
|
||||
|
||||
@@ -3560,6 +3560,7 @@ view =
|
||||
.opening-ocs-discussions-for-help-and-questions = ヘルプと質問のため OCS ディスカッションを開いています...
|
||||
.opening-ocs-web = OCS Web を開いています...
|
||||
.opening-the-command-reference = コマンドリファレンスを開いています...
|
||||
.already-loaded-different-contents-kept = "__ocs_fmt_0__" は既に別の内容で読み込まれています。既存のファイルを保持しました。
|
||||
.opening-patreon-page = Patreon ページを開いています...
|
||||
.opening-feedback-page = フィードバックページを開いています...
|
||||
.opening-release-notes = リリースノートを開いています...
|
||||
|
||||
@@ -3571,6 +3571,7 @@ view =
|
||||
.opening-ocs-discussions-for-help-and-questions = OCS 열기 도움말 및 질문에 대한 토론...
|
||||
.opening-ocs-web = OCS 웹을 여는 중...
|
||||
.opening-the-command-reference = 명령 참조 열기...
|
||||
.already-loaded-different-contents-kept = "__ocs_fmt_0__"이(가) 이미 다른 내용으로 로드되었습니다. 기존 파일을 유지했습니다.
|
||||
.opening-patreon-page = Patreon 페이지를 여는 중...
|
||||
.opening-feedback-page = 피드백 페이지를 여는 중...
|
||||
.opening-release-notes = 출시 노트를 여는 중...
|
||||
|
||||
@@ -3570,6 +3570,7 @@ view =
|
||||
.opening-ocs-discussions-for-help-and-questions = Openen OCS Discussie voor hulp en vragen...
|
||||
.opening-ocs-web = Openen OCS Web...
|
||||
.opening-the-command-reference = Commandoreferentie openen...
|
||||
.already-loaded-different-contents-kept = "__ocs_fmt_0__" is al geladen met andere inhoud; het bestaande bestand is bewaard.
|
||||
.opening-patreon-page = Patreon pagina openen...
|
||||
.opening-feedback-page = Herinneringspagina openen...
|
||||
.opening-release-notes = Openen van de release notes...
|
||||
|
||||
@@ -3571,6 +3571,7 @@ view =
|
||||
.opening-ocs-discussions-for-help-and-questions = Otwieranie dyskusji OCS w celu uzyskania pomocy i pytań...
|
||||
.opening-ocs-web = Otwieranie OCS Web...
|
||||
.opening-the-command-reference = Otwieranie dokumentacji poleceń...
|
||||
.already-loaded-different-contents-kept = "__ocs_fmt_0__" jest już wczytany z inną zawartością; zachowano istniejący plik.
|
||||
.opening-patreon-page = Otwieranie strony Patreon...
|
||||
.opening-feedback-page = Otwieram stronę z opiniami...
|
||||
.opening-release-notes = Otwieram informacje o wersji...
|
||||
|
||||
@@ -3572,6 +3572,7 @@ view =
|
||||
.opening-ocs-discussions-for-help-and-questions = Abrindo Discussões do OCS para ajuda e perguntas...
|
||||
.opening-ocs-web = Abrindo OCS Web...
|
||||
.opening-the-command-reference = Abrindo a referência de comandos...
|
||||
.already-loaded-different-contents-kept = "__ocs_fmt_0__" já está carregado com conteúdo diferente; o ficheiro existente foi mantido.
|
||||
.opening-patreon-page = Abrindo página do Patreon...
|
||||
.opening-feedback-page = Abrindo página de comentários...
|
||||
.opening-release-notes = Abrindo notas da versão...
|
||||
|
||||
@@ -3573,6 +3573,7 @@ view =
|
||||
.opening-ocs-discussions-for-help-and-questions = Открытие OCS Дискуссии о помощи и вопросах
|
||||
.opening-ocs-web = Открытие OCS Веб-сайт
|
||||
.opening-the-command-reference = Открытие справочника команд...
|
||||
.already-loaded-different-contents-kept = "__ocs_fmt_0__" уже загружен с другим содержимым; существующий файл сохранён.
|
||||
.opening-patreon-page = Открытие страницы Patreon...
|
||||
.opening-feedback-page = Открываем страницу обратной связи...
|
||||
.opening-release-notes = Открытие выпускных заметок...
|
||||
|
||||
@@ -3522,6 +3522,7 @@ view =
|
||||
.opening-ocs-discussions-for-help-and-questions = Yardım ve sorular için OCS Tartışmaları açılıyor...
|
||||
.opening-ocs-web = OCS Web açılıyor...
|
||||
.opening-the-command-reference = Komut başvurusu açılıyor...
|
||||
.already-loaded-different-contents-kept = "__ocs_fmt_0__" zaten farklı içerikle yüklü; mevcut dosya tutuldu.
|
||||
.opening-patreon-page = Patreon sayfası açılıyor...
|
||||
.opening-feedback-page = Geri bildirim sayfası açılıyor...
|
||||
.opening-release-notes = Sürüm notları açılıyor...
|
||||
|
||||
@@ -3555,6 +3555,7 @@ view =
|
||||
.opening-ocs-discussions-for-help-and-questions = 开幕OCS 讨论帮助和问题...
|
||||
.opening-ocs-web = 打开OCS网络...
|
||||
.opening-the-command-reference = 正在打开命令参考...
|
||||
.already-loaded-different-contents-kept = "__ocs_fmt_0__"已以不同内容加载;保留了现有文件。
|
||||
.opening-patreon-page = 打开帕特伦的页面...
|
||||
.opening-feedback-page = 打开反馈页面...
|
||||
.opening-release-notes = 打开发行笔记...
|
||||
|
||||
@@ -3567,6 +3567,7 @@ view =
|
||||
.opening-ocs-discussions-for-help-and-questions = 開幕OCS 討論幫助和問題...
|
||||
.opening-ocs-web = 開啟OCS網路...
|
||||
.opening-the-command-reference = 正在開啟命令參考...
|
||||
.already-loaded-different-contents-kept = "__ocs_fmt_0__"已以不同內容載入;保留了現有檔案。
|
||||
.opening-patreon-page = 開啟帕特倫的頁面...
|
||||
.opening-feedback-page = 開啟反饋頁面...
|
||||
.opening-release-notes = 開啟發行筆記...
|
||||
|
||||
@@ -1589,6 +1589,16 @@ impl OpenCADStudio {
|
||||
.pick_files()
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
// Each picked file becomes a full parsed document held in
|
||||
// the registry for the session; a bound keeps a giant
|
||||
// multi-pick from exhausting the wasm heap (tab death).
|
||||
const MAX_EXTRACTION_SOURCES: usize = 16;
|
||||
if handles.len() > MAX_EXTRACTION_SOURCES {
|
||||
return Err(crate::tf!(
|
||||
"At most {MAX_EXTRACTION_SOURCES} drawings can be added in the browser."
|
||||
)
|
||||
.into_owned());
|
||||
}
|
||||
let mut paths = Vec::new();
|
||||
for handle in handles {
|
||||
let name = handle.file_name();
|
||||
|
||||
@@ -2283,6 +2283,13 @@ impl OpenCADStudio {
|
||||
}
|
||||
return self.continue_tab_close_queue();
|
||||
}
|
||||
} else if matches!(self.pending_close, Some(crate::app::PendingClose::Quit)) {
|
||||
// Mirror the native Quit continuation: offer to save the
|
||||
// next dirty tab, or finish the quit.
|
||||
if self.tabs.iter().any(|tab| tab.dirty) {
|
||||
return self.open_unsaved_dialog_window();
|
||||
}
|
||||
return Task::batch([self.close_unsaved_dialog_window(), self.exit_app()]);
|
||||
} else if self.pending_close.is_some() {
|
||||
return self.open_unsaved_dialog_window();
|
||||
}
|
||||
|
||||
@@ -572,6 +572,16 @@ impl OpenCADStudio {
|
||||
.file_name()
|
||||
.map(|name| name.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| path.to_string_lossy().into_owned());
|
||||
// The start page stays clickable under the open overlay,
|
||||
// so a recent can race an in-flight open and clobber it
|
||||
// (its WebFileOpened then fails the id check and the
|
||||
// bytes are lost) — refuse like on_open_file does.
|
||||
if self.opening.is_some() {
|
||||
self.command_line.push_info(
|
||||
crate::t!("An open is already in progress.").as_ref(),
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
let state = std::sync::Arc::new(crate::io::OpenProgressState::new(
|
||||
crate::app::OPEN_PHASE_READING,
|
||||
));
|
||||
|
||||
@@ -290,3 +290,20 @@ fn is_image_name(name: &str) -> bool {
|
||||
"png" | "jpg" | "jpeg" | "bmp" | "tiff" | "tif"
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::is_drawing_name;
|
||||
|
||||
#[test]
|
||||
fn drawing_names_use_the_worker_convention() {
|
||||
assert!(is_drawing_name("plan.dwg"));
|
||||
assert!(is_drawing_name("PLAN.DXF"));
|
||||
assert!(is_drawing_name("DWG"));
|
||||
assert!(is_drawing_name("dxf"));
|
||||
assert!(!is_drawing_name("plan.png"));
|
||||
assert!(!is_drawing_name("dwgx"));
|
||||
assert!(!is_drawing_name("plan.dwg.txt"));
|
||||
assert!(!is_drawing_name(""));
|
||||
}
|
||||
}
|
||||
@@ -312,3 +312,23 @@ pub(crate) async fn modified_ms(name: &str) -> Option<f64> {
|
||||
.ok()?;
|
||||
Some(file.last_modified())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::name_hash;
|
||||
|
||||
/// Canonical FNV-1a 64 over the raw name bytes — the reference vector
|
||||
/// from the spec's test suite.
|
||||
#[test]
|
||||
fn name_hash_is_fnv1a64() {
|
||||
assert_eq!(name_hash(""), "cbf29ce484222325");
|
||||
assert_eq!(name_hash("a"), "af63dc4c8601ec8c");
|
||||
assert_eq!(name_hash("foobar"), "85944171f73967e8");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn name_hash_distinguishes_case_and_extension() {
|
||||
assert_ne!(name_hash("a.dwg"), name_hash("A.dwg"));
|
||||
assert_ne!(name_hash("a.dwg"), name_hash("a.dxf"));
|
||||
}
|
||||
}
|
||||
@@ -790,6 +790,17 @@ fn set_handle(entity: &mut EntityType, h: Handle) {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// The registries are process-global and the default harness runs tests
|
||||
/// in parallel; the eviction test's inserts can evict a sibling test's
|
||||
/// entry mid-assert. Registry tests hold this lock for their duration.
|
||||
fn registry_guard() -> std::sync::MutexGuard<'static, ()> {
|
||||
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
|
||||
LOCK.get_or_init(|| Mutex::new(()))
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner())
|
||||
}
|
||||
|
||||
|
||||
fn outcome() -> acadrust::ReadOutcome {
|
||||
acadrust::ReadOutcome::new(CadDocument::new(), acadrust::ReadStats::default())
|
||||
}
|
||||
@@ -821,6 +832,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn registry_finds_a_registered_source_by_trailing_name() {
|
||||
let _registry = registry_guard();
|
||||
let registered = "Registry-Find-Site.dwg";
|
||||
register_source(registered, Arc::new(vec![1, 2, 3]));
|
||||
// The drawing's stored path is the author's absolute foreign path;
|
||||
@@ -840,6 +852,7 @@ mod tests {
|
||||
/// re-registration of a live name neither evicts nor duplicates it.
|
||||
#[test]
|
||||
fn source_registry_evicts_oldest_beyond_the_cap() {
|
||||
let _registry = registry_guard();
|
||||
for i in 0..(SOURCE_CAP + 2) {
|
||||
register_source(&format!("f{i}.dwg"), Arc::new(vec![i as u8]));
|
||||
}
|
||||
@@ -879,6 +892,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn register_source_supersedes_a_cached_parse_of_the_same_file() {
|
||||
let _registry = registry_guard();
|
||||
let name = "Registry-Supersede-Plan.dwg";
|
||||
register_parsed(name, outcome());
|
||||
// Re-registering the same file under a differently-cased path name
|
||||
|
||||
@@ -1885,6 +1885,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
|
||||
"Opening OCS Discussions for help and questions..." => Some(("view", "opening-ocs-discussions-for-help-and-questions")),
|
||||
"Opening OCS Web..." => Some(("view", "opening-ocs-web")),
|
||||
"Opening the command reference..." => Some(("view", "opening-the-command-reference")),
|
||||
"\"{name}\" is already loaded with different contents; the existing file was kept." => Some(("file", "already-loaded-different-contents-kept")),
|
||||
"Opening Patreon page..." => Some(("view", "opening-patreon-page")),
|
||||
"Opening feedback page..." => Some(("view", "opening-feedback-page")),
|
||||
"Opening file" => Some(("ui", "opening-file")),
|
||||
|
||||
Reference in new issue
Block a user