Files
wordcraft/crates/engine/src/packs/store.rs
T
Martin BallandClaude Opus 5.5 72327bdd36 CP4.4: pin language-packs-1 index
craft-i18n's first Language pack release is published
(https://github.com/storytold/craft-i18n/releases/tag/language-packs-1, 61 packs,
built from craft-i18n main 4504e288). PACK_INDEX now pins its index.json sha256
instead of the all-zero placeholder. Docs say which languages it covers and which
wait on craft-proof (compounding) or are excluded on licence grounds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FypgCX8LsYYE8fsj4byBm2
2026-11-02 09:40:07 +00:00

105 lines
4.0 KiB
Rust

//! The font store as the packs [`Backend`](super::Backend): its folder, its consent setting, the
//! pinned pack index and verified installs (craft-libs `craft-font-store`, kind `language-pack`).
use std::path::PathBuf;
use std::sync::{Mutex, PoisonError};
use craft_font_catalog::FontEnv;
use craft_font_store::{FontStore, Network, PackEntry, PackIndex, PackIndexPin};
use super::{Cancel, Pack};
/// The pack index this build trusts: craft-i18n's first Language pack release, language-packs-1
/// (61 packs, built from craft-i18n `4504e288`), pinned by the sha256 of its `index.json`.
/// `CRAFT_LANGUAGE_PACK_INDEX` (read by the font store itself) points WordCraft at a local or test
/// index instead, for trying packs before they are released.
pub const PACK_INDEX: PackIndexPin = PackIndexPin {
url: "https://github.com/storytold/craft-i18n/releases/download/language-packs-1/index.json",
sha256: "531465b50126d0594bb89a9c4512f7ae67d581259636040a410190641ec1428c",
};
pub(super) struct FontStoreBackend {
store: FontStore,
/// The index last read or fetched; installs take their entry (url, size, sha256) from it.
index: Mutex<Option<PackIndex>>,
}
impl FontStoreBackend {
pub(super) fn new(user: bool) -> Option<Self> {
let env = FontEnv::from_process();
let store = if user { FontStore::for_user(&env)? } else { FontStore::for_agent(&env)? };
Some(Self { store, index: Mutex::new(None) })
}
/// A store in `dir`.
pub(super) fn in_dir(dir: PathBuf, user: bool) -> Self {
let caller = if user { craft_font_store::Caller::User } else { craft_font_store::Caller::Agent };
Self { store: FontStore::new(dir, caller), index: Mutex::new(None) }
}
fn keep(&self, index: PackIndex) -> Vec<Pack> {
let packs = index.packs.iter().map(pack).collect();
*self.index.lock().unwrap_or_else(PoisonError::into_inner) = Some(index);
packs
}
}
fn pack(e: &PackEntry) -> Pack {
Pack {
locale: e.locale.clone(),
name: e.name.clone(),
version: e.version.clone(),
url: e.url.clone(),
// TODO(CP4): the index's optional `source` once craft-font-store's PackEntry carries it.
source: None,
size: e.size,
licence: e.licence.clone(),
class: e.class.clone(),
spelling: e.spelling,
hyphenation: e.hyphenation,
}
}
impl super::Backend for FontStoreBackend {
fn index(&self) -> Result<Vec<Pack>, String> {
Ok(self.store.cached_language_pack_index(&PACK_INDEX).map(|i| self.keep(i)).unwrap_or_default())
}
fn fetch_index(&self, cancel: &Cancel) -> Result<Vec<Pack>, String> {
let index = self.store.fetch_language_pack_index(&PACK_INDEX, &cancel.0).map_err(|e| e.to_string())?;
Ok(self.keep(index))
}
fn network_on(&self) -> bool {
self.store.network() == Network::On
}
fn enable_network(&self) -> Result<(), String> {
self.store.enable_network().map_err(|e| e.to_string())
}
fn install(&self, pack: &Pack, cancel: &Cancel, progress: &mut dyn FnMut(u64, u64)) -> Result<(), String> {
let index = {
let held = self.index.lock().unwrap_or_else(PoisonError::into_inner).clone();
match held {
Some(i) => i,
None => self.store.cached_language_pack_index(&PACK_INDEX).ok_or("the list of Language packs hasn't been fetched")?,
}
};
let entry = index.pack(&pack.locale).ok_or_else(|| format!("there is no language pack for {}", pack.locale))?.clone();
self.store.install_language_pack(&index, &entry, &cancel.0, &mut |p| progress(p.done, p.total)).map(|_| ()).map_err(|e| e.to_string())
}
fn remove(&self, locale: &str) -> Result<(), String> {
self.store.remove_language_pack(locale).map_err(|e| e.to_string())
}
fn index_override(&self) -> Option<String> {
craft_font_store::language_pack_index_overridden()
}
fn packs_dir(&self) -> Option<PathBuf> {
Some(self.store.language_packs_dir())
}
}