use crate::config::VpxConfig;
use crate::db::Database;
pub const FIRST_REVISION_WITH_IDS: u32 = 5515;
pub fn revision_from_stamp(stamp: &str) -> Option<u32> {
let stamp = stamp.trim();
if stamp.len() <= 4 || !stamp.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
stamp[4..].parse().ok()
}
pub fn revision_from_tag(tag: &str) -> Option<u32> {
tag.trim().split('-').nth(1)?.parse().ok()
}
pub fn revision(config: &VpxConfig, db: &Database) -> Option<u32> {
db.get_config("vpx_installed_tag")
.and_then(|t| revision_from_tag(&t))
.or_else(|| {
config
.get("Version", "VPinball")
.and_then(|s| revision_from_stamp(&s))
})
}
pub fn uses_ids(config: &VpxConfig, db: &Database) -> bool {
revision(config, db).is_none_or(|r| r >= FIRST_REVISION_WITH_IDS)
}
pub const FIRST_REVISION_WITHOUT_B2S_LEGACY: u32 = 6293;
pub fn value(name: &str, x: i32, y: i32, ids: bool) -> String {
if ids {
format!("{name} [{x}, {y}]")
} else {
name.to_string()
}
}
pub fn name_of(value: &str) -> &str {
let v = value.trim_end();
if let Some(open) = v.rfind(" [")
&& v.ends_with(']')
{
let inner = &v[open + 2..v.len() - 1];
let mut parts = inner.split(',').map(str::trim);
let is_int = |s: Option<&str>| s.is_some_and(|s| s.parse::<i32>().is_ok());
if is_int(parts.next()) && is_int(parts.next()) && parts.next().is_none() {
return &v[..open];
}
}
v
}
pub fn matches(value: &str, name: &str, x: i32, y: i32) -> bool {
let v = value.trim_end();
if name_of(v) != name {
return false;
}
v == name || v == self::value(name, x, y, true)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn revision_comes_from_the_stamp_and_the_tag() {
assert_eq!(revision_from_stamp("10815430"), Some(5430));
assert_eq!(revision_from_stamp(" 10816501 "), Some(6501));
assert_eq!(revision_from_stamp("1081"), None);
assert_eq!(revision_from_stamp("10.8.1"), None);
assert_eq!(revision_from_tag("v10.8.1-6501-3d9540861"), Some(6501));
assert_eq!(revision_from_tag("v10.8.1"), None);
}
#[test]
fn ids_carry_the_position() {
assert_eq!(value("DP-1 42\"", 3840, 0, true), "DP-1 42\" [3840, 0]");
assert_eq!(value("DP-1 42\"", 3840, 0, false), "DP-1 42\"");
assert_eq!(value("HDMI-1", -1920, 0, true), "HDMI-1 [-1920, 0]");
}
#[test]
fn the_name_reads_back_from_both_forms() {
assert_eq!(name_of("DP-1 42\" [3840, 0]"), "DP-1 42\"");
assert_eq!(name_of("HDMI-1 [-1920, 0]"), "HDMI-1");
assert_eq!(name_of("DP-1 42\""), "DP-1 42\"");
assert_eq!(name_of("Panel [rev B]"), "Panel [rev B]");
}
#[test]
fn two_identical_screens_are_told_apart_by_position() {
let v = "MetaVendor [3840, 0]";
assert!(matches(v, "MetaVendor", 3840, 0));
assert!(!matches(v, "MetaVendor", 0, 0));
assert!(matches("MetaVendor", "MetaVendor", 0, 0));
assert!(!matches(v, "Other", 3840, 0));
}
}