← Commit history

2.10.2: Manager walks the full wiki page list (offset paging) and keys My projects to the wiki identity

John Lauer ·697cb1bf64 ·1mo ago ·parent ac99a0c
6 files changed +72−36
Cargo.lock+1−1
@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"  [[package]] name = "adom-lbr"-version = "2.10.1"+version = "2.10.2" dependencies = [  "altium-codec",  "anyhow",
Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "adom-lbr"-version = "2.10.1"+version = "2.10.2" edition = "2021" description = "adom-lbr generator / EDA translator: the canonical adom-lbr JSON is the hub, with native converters to Altium (.SchLib/.PcbLib/.IntLib), EAGLE/Fusion (.lbr), and KiCad. Altium output is written natively — no Altium needed at conversion time." 
VERSION+1−1
@@ -1 +1 @@-2.10.1+2.10.2
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-lbr",-  "version": "2.10.1",+  "version": "2.10.2",   "type": "app",   "description": "adom-lbr \u2014 the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad \u21c4 Altium \u21c4 EAGLE/Fusion) through one canonical adom-lbr JSON \u2014 symbol + footprint + 3D + pin\u2194pad map. Altium is written natively (no Altium needed). Includes the adom-lbr Manager: browse wiki components and view symbol/footprint/3D + the per-EDA layer stackup.",   "dependencies": {},
page.json+8−10
@@ -1,8 +1,8 @@ {   "slug": "adom-lbr",-  "version": "2.10.1",+  "version": "2.10.2",   "type": "app",-  "description": "adom-lbr — the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad ⇄ Altium ⇄ EAGLE/Fusion) through one canonical adom-lbr JSON — symbol + footprint + 3D + pin↔pad map. Altium is written natively (no Altium needed). Includes the adom-lbr Manager: browse wiki components and view symbol/footprint/3D + the per-EDA layer stackup.",+  "description": "adom-lbr \u2014 the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad \u21c4 Altium \u21c4 EAGLE/Fusion) through one canonical adom-lbr JSON \u2014 symbol + footprint + 3D + pin\u2194pad map. Altium is written natively (no Altium needed). Includes the adom-lbr Manager: browse wiki components and view symbol/footprint/3D + the per-EDA layer stackup.",   "dependencies": {},   "tags": [     "eda",@@ -52,23 +52,21 @@   ],   "files": [     "bin/**",-    "assets/**",     "SKILL.md",-    "skills/**",     "README.md",     "install.sh",     "uninstall.sh",     "screenshots/**"   ],-  "discovery_pitch": "Convert electronic-component symbols + footprints between any two EDA tools — KiCad, Altium (written natively, no Altium needed), and EAGLE/Fusion — through one canonical adom-lbr JSON, silk/courtyard/fab layers preserved. Plus a Manager to browse and inspect any wiki part.",+  "discovery_pitch": "Convert electronic-component symbols + footprints between any two EDA tools \u2014 KiCad, Altium (written natively, no Altium needed), and EAGLE/Fusion \u2014 through one canonical adom-lbr JSON, silk/courtyard/fab layers preserved. Plus a Manager to browse and inspect any wiki part.",   "sample_prompts": [     {       "prompt": "Convert this KiCad symbol and footprint to an Altium SchLib and PcbLib",-      "label": "KiCad → Altium"+      "label": "KiCad \u2192 Altium"     },     {       "prompt": "Import this Altium footprint into the adom-lbr JSON",-      "label": "Altium → JSON"+      "label": "Altium \u2192 JSON"     },     {       "prompt": "Bundle this part into a single Altium IntLib",@@ -76,15 +74,15 @@     },     {       "prompt": "Convert this Altium SchLib and PcbLib to a KiCad symbol and footprint",-      "label": "Altium → KiCad"+      "label": "Altium \u2192 KiCad"     },     {       "prompt": "Export this adom-lbr JSON to a KiCad .kicad_sym and .kicad_mod",-      "label": "JSON → KiCad"+      "label": "JSON \u2192 KiCad"     },     {       "prompt": "Open the adom-lbr Manager to browse wiki components and view the layer stackup",       "label": "Browse parts"     }   ]-}\ No newline at end of file+}
src/manage.rs+60−22
@@ -20,10 +20,23 @@ use tiny_http::{Header, Method, Response, Server}; const WIKI: &str = "https://wiki.adom.inc"; // Whose components the Manager highlights as "mine". Hard-coding one developer's // handle showed everyone else somebody else's library as their own.+/// Who "My projects" belongs to. ADOM_LBR_OWNER overrides; otherwise the wiki+/// identity this container is signed in as (`adom-wiki whoami` displayName,+/// which is what the pages API reports as author_name). Without this the+/// section was empty for everyone, since nobody sets the env var. fn my_owner() -> String {-    std::env::var("ADOM_LBR_OWNER")-        .ok()-        .filter(|v| !v.trim().is_empty())+    if let Some(v) = std::env::var("ADOM_LBR_OWNER").ok().filter(|v| !v.trim().is_empty()) {+        return v;+    }+    let out = Command::new("adom-wiki").arg("whoami").output().ok();+    out.filter(|o| o.status.success())+        .and_then(|o| serde_json::from_slice::<Value>(&o.stdout).ok())+        .and_then(|v| {+            v.pointer("/user/displayName")+                .or_else(|| v.pointer("/user/username"))+                .and_then(|s| s.as_str())+                .map(|s| s.to_string())+        })         .unwrap_or_default() } @@ -137,30 +150,55 @@ fn up(slug: &str) -> String {     slug.to_uppercase() } +/// One page of the wiki pages API. The server caps every call at 200 rows no+/// matter what `limit` says, and reports `total`, so the caller walks `offset`.+fn list_page(offset: usize) -> (Vec<Value>, usize) {+    let Some(body) = curl(&format!("{WIKI}/api/v1/pages?type=component&limit=200&offset={offset}")) else {+        return (Vec::new(), 0);+    };+    let Ok(v) = serde_json::from_slice::<Value>(&body) else { return (Vec::new(), 0) };+    let total = v.get("total").and_then(|t| t.as_u64()).unwrap_or(0) as usize;+    let pages = v.get("pages").and_then(|p| p.as_array()).cloned().unwrap_or_default();+    (pages, total)+}+ /// List component pages from the wiki API (fast + reliable; `adompkg` is dead).+/// Walks every 200-row page, in parallel after the first, because a single+/// `limit=300` call silently returned the first 200 of ~800 pages, and every+/// board/molecule page (the Manager's whole top level) lived past the cap, so+/// "My projects" read 0 for everyone. fn list_components() -> Value {-    let mut items = Vec::new();-    if let Some(body) = curl(&format!("{WIKI}/api/v1/pages?type=component&limit=300")) {-        if let Ok(v) = serde_json::from_slice::<Value>(&body) {-            if let Some(pages) = v.get("pages").and_then(|p| p.as_array()) {-                for pg in pages {-                    let slug = pg.get("slug").and_then(|s| s.as_str()).unwrap_or("").to_string();-                    if slug.is_empty() {-                        continue;-                    }-                    let title = pg.get("title").and_then(|s| s.as_str()).unwrap_or(&slug).to_string();-                    let brief = pg.get("brief").and_then(|s| s.as_str()).unwrap_or("").to_string();-                    // tags may arrive as a JSON-string or a real array-                    let tags = pg.get("tags").map(|t| match t.as_str() {-                        Some(s) => serde_json::from_str::<Value>(s).unwrap_or_else(|_| json!([])),-                        None => t.clone(),-                    }).unwrap_or_else(|| json!([]));-                    let owner = pg.get("author_name").and_then(|s| s.as_str()).unwrap_or("").to_string();-                    items.push(json!({"slug": slug, "title": title, "brief": brief, "tags": tags, "owner": owner}));-                }+    let (first, total) = list_page(0);+    let mut raw = first;+    if total > raw.len() && !raw.is_empty() {+        let step = raw.len();+        let handles: Vec<_> = (step..total)+            .step_by(step)+            .map(|off| std::thread::spawn(move || list_page(off).0))+            .collect();+        for h in handles {+            if let Ok(pages) = h.join() {+                raw.extend(pages);             }         }     }+    let mut seen = std::collections::HashSet::new();+    let mut items = Vec::new();+    for pg in &raw {+        let slug = pg.get("slug").and_then(|s| s.as_str()).unwrap_or("").to_string();+        if slug.is_empty() || !seen.insert(slug.clone()) {+            continue;+        }+        let title = pg.get("title").and_then(|s| s.as_str()).unwrap_or(&slug).to_string();+        let brief = pg.get("brief").and_then(|s| s.as_str()).unwrap_or("").to_string();+        // tags may arrive as a JSON-string or a real array+        let tags = pg.get("tags").map(|t| match t.as_str() {+            Some(s) => serde_json::from_str::<Value>(s).unwrap_or_else(|_| json!([])),+            None => t.clone(),+        }).unwrap_or_else(|| json!([]));+        let owner = pg.get("author_name").and_then(|s| s.as_str()).unwrap_or("").to_string();+        items.push(json!({"slug": slug, "title": title, "brief": brief, "tags": tags, "owner": owner}));+    }     if items.is_empty() {         for s in ["cl21a476mqynnne", "cq03saf4702t5e", "lqm18pnr47mfhd", "machinepinlargestandard", "machinecontactmedium"] {             items.push(json!({"slug": s, "title": up(s), "brief": ""}));