Store kind "strings": string packs install like themes and plugins (dropped .strings.json, links, the signed Store), validated as plain data on this side (envelope, plain text, size caps, the app's own site), kept in app data strings/ with their order and which are on (at most 8); licence and credits are optional fields of every catalog item; five shell-only commands (strings_list, strings_read, strings_set, strings_remove, strings_apply); the shell's delivery (packs + the inventory slice) goes into site tabs' session storage by revision, as the first document-start layer of a site tab and never in safe mode
This commit is contained in:
parent
fd6ae707ca
commit
c3bfc7a6f8
10 changed files with 593 additions and 13 deletions
|
|
@ -6,6 +6,7 @@
|
|||
use super::catalog::{
|
||||
self as dist, Catalog, CatalogItem, InstallLink, InstallRecord, InstallRecords, Kind, SourceKind, StoreConfig, ThemeInfo,
|
||||
};
|
||||
use super::strings::{self as packs, StringsInfo, StringsList, StringsStore};
|
||||
use crate::plugins::{self, PluginHost, PluginStore, ScriptPlugin, PLUGIN_SUFFIX};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeMap, HashMap, VecDeque};
|
||||
|
|
@ -72,6 +73,8 @@ pub struct InstallPreview {
|
|||
pub version: String,
|
||||
pub plugin: Option<ScriptPlugin>,
|
||||
pub theme: Option<ThemeInfo>,
|
||||
/// String packs: what the pack says about itself (the shell adds coverage from the inventory).
|
||||
pub strings: Option<StringsInfo>,
|
||||
pub findings: Vec<&'static str>,
|
||||
pub existing: Option<Existing>,
|
||||
pub store: Option<CatalogItem>,
|
||||
|
|
@ -85,6 +88,8 @@ pub struct InstallOutcome {
|
|||
pub plugins: Option<Vec<ScriptPlugin>>,
|
||||
/// Themes: the exact JSON the owner confirmed; the shell adds it (sanitized) to its themes.
|
||||
pub theme_text: Option<String>,
|
||||
/// String packs: the installed packs after installing.
|
||||
pub strings: Option<StringsList>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
|
|
@ -191,6 +196,11 @@ impl Store {
|
|||
PluginStore::new(self.data_dir.join("plugins"), self.plugins)
|
||||
}
|
||||
|
||||
/// The installed string packs (`<data>/strings`).
|
||||
pub fn strings(&self) -> StringsStore {
|
||||
StringsStore::new(&self.data_dir)
|
||||
}
|
||||
|
||||
fn plugins_dir(&self) -> PathBuf {
|
||||
self.data_dir.join("plugins")
|
||||
}
|
||||
|
|
@ -234,7 +244,7 @@ impl Store {
|
|||
let link = dist::parse_install_link(self.config, &dist::install_link(self.config, kind, url))?;
|
||||
let file = match kind {
|
||||
Kind::Plugin => Some(dist::plugin_file_from_url(&Url::parse(&link.url).map_err(|_| "VALIDATION: invalid URL")?)?),
|
||||
Kind::Theme => None,
|
||||
Kind::Theme | Kind::Strings => None,
|
||||
};
|
||||
Ok((link, file))
|
||||
}
|
||||
|
|
@ -254,6 +264,7 @@ impl Store {
|
|||
};
|
||||
}
|
||||
let text = String::from_utf8(bytes.clone()).map_err(|_| "VALIDATION: not UTF-8 text")?;
|
||||
let mut strings = None;
|
||||
let (plugin, theme, findings, version, existing) = match kind {
|
||||
Kind::Plugin => {
|
||||
let file = file.as_deref().ok_or("VALIDATION: missing plugin file name")?;
|
||||
|
|
@ -267,6 +278,22 @@ impl Store {
|
|||
let version = validated.meta.version.clone();
|
||||
(Some(validated.meta), None, validated.findings, version, existing)
|
||||
}
|
||||
Kind::Strings => {
|
||||
let info = packs::validate_strings(&bytes)?;
|
||||
if info.site != self.config.app_id {
|
||||
return Err(format!("VALIDATION: this string pack is for another app ({})", info.site));
|
||||
}
|
||||
let id = packs::pack_id(&info, store.as_ref().map(|item| item.id.as_str()));
|
||||
let local = self.strings();
|
||||
let existing = local.read(&id).map(|old| Existing {
|
||||
version: packs::validate_strings(&old).map(|old| old.version).unwrap_or_default(),
|
||||
enabled: local.state().enabled.contains(&id),
|
||||
same: dist::sha256_hex(&old) == dist::sha256_hex(&bytes),
|
||||
});
|
||||
let version = if info.version.is_empty() { store.as_ref().map(|item| item.version.clone()).unwrap_or_default() } else { info.version.clone() };
|
||||
strings = Some(info);
|
||||
(None, None, Vec::new(), version, existing)
|
||||
}
|
||||
Kind::Theme => {
|
||||
let info = dist::validate_theme(&bytes)?;
|
||||
let version = store.as_ref().map(|item| item.version.clone()).unwrap_or_default();
|
||||
|
|
@ -291,6 +318,7 @@ impl Store {
|
|||
version: version.clone(),
|
||||
plugin,
|
||||
theme,
|
||||
strings,
|
||||
findings,
|
||||
existing,
|
||||
store: store.clone(),
|
||||
|
|
@ -316,10 +344,12 @@ impl Store {
|
|||
let lower = name.to_ascii_lowercase();
|
||||
let (kind, file) = if lower.ends_with(PLUGIN_SUFFIX) {
|
||||
(Kind::Plugin, Some(name.to_owned()))
|
||||
} else if lower.ends_with(".strings.json") {
|
||||
(Kind::Strings, None)
|
||||
} else if lower.ends_with(".json") {
|
||||
(Kind::Theme, None)
|
||||
} else {
|
||||
return Err("VALIDATION: drop a theme (.json) or a plugin (.plugin.js)".into());
|
||||
return Err("VALIDATION: drop a theme (.json), a plugin (.plugin.js) or a string pack (.strings.json)".into());
|
||||
};
|
||||
if text.len() > kind.max_bytes() {
|
||||
return Err("VALIDATION: the file is larger than allowed".into());
|
||||
|
|
@ -393,7 +423,18 @@ impl Store {
|
|||
store.set_enabled(&file, true)?;
|
||||
run(&script);
|
||||
}
|
||||
Ok(InstallOutcome { kind: Kind::Plugin, plugins: Some(store.list()?), theme_text: None })
|
||||
Ok(InstallOutcome { kind: Kind::Plugin, plugins: Some(store.list()?), theme_text: None, strings: None })
|
||||
}
|
||||
Kind::Strings => {
|
||||
let info = packs::validate_strings(&staged.bytes)?;
|
||||
let id = packs::pack_id(&info, staged.store.as_ref().map(|item| item.id.as_str()));
|
||||
let local = self.strings();
|
||||
local.install(&id, &staged.bytes, enable)?;
|
||||
inner.records.insert(InstallRecords::key(Kind::Strings, &id), record(staged.version.clone()));
|
||||
let records = inner.records.clone();
|
||||
drop(inner);
|
||||
self.save_records(&records);
|
||||
Ok(InstallOutcome { kind: Kind::Strings, plugins: None, theme_text: None, strings: Some(local.list()) })
|
||||
}
|
||||
Kind::Theme => {
|
||||
let id = theme_id.filter(|id| valid_theme_id(id)).ok_or("VALIDATION: invalid theme id")?;
|
||||
|
|
@ -402,7 +443,7 @@ impl Store {
|
|||
drop(inner);
|
||||
self.save_records(&records);
|
||||
let text = String::from_utf8(staged.bytes).map_err(|_| "VALIDATION: not UTF-8")?;
|
||||
Ok(InstallOutcome { kind: Kind::Theme, plugins: None, theme_text: Some(text) })
|
||||
Ok(InstallOutcome { kind: Kind::Theme, plugins: None, theme_text: Some(text), strings: None })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -421,6 +462,19 @@ impl Store {
|
|||
store.list()
|
||||
}
|
||||
|
||||
/// Removes an installed string pack and forgets where it came from.
|
||||
pub fn remove_strings(&self, id: &str) -> Result<StringsList, String> {
|
||||
let local = self.strings();
|
||||
local.remove(id)?;
|
||||
let mut inner = self.lock();
|
||||
if inner.records.items.remove(&InstallRecords::key(Kind::Strings, id)).is_some() {
|
||||
let records = inner.records.clone();
|
||||
drop(inner);
|
||||
self.save_records(&records);
|
||||
}
|
||||
Ok(local.list())
|
||||
}
|
||||
|
||||
/// Where installed plugins and themes came from (for badges and update checks).
|
||||
pub fn records(&self) -> InstallRecords {
|
||||
self.lock().records.clone()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue