1use crate::parser::parse_config;
2use crate::*;
3
4use std::collections::{BinaryHeap, HashSet};
5use std::fs;
6use std::path::{Path, PathBuf};
7
8#[derive(Clone, Debug, PartialEq)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub enum ConfigPart {
11 Description(String),
12 SelectFont(SelectFont),
13 Dir(Dir),
14 CacheDir(CacheDir),
15 Include(Include),
16 Match(Match),
17 Config(Config),
18 Alias(Alias),
19 RemapDir(RemapDir),
20 ResetDirs,
21}
22
23#[derive(Clone, Debug, Default, PartialEq)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25pub struct FontConfig {
26 pub select_fonts: Vec<SelectFont>,
27 pub dirs: Vec<DirData>,
28 pub cache_dirs: Vec<PathBuf>,
29 pub remap_dirs: Vec<RemapDirData>,
30 pub matches: Vec<Match>,
31 pub config: Config,
32 pub aliases: Vec<Alias>,
33 pub config_files: HashSet<PathBuf>,
34}
35
36impl FontConfig {
37 pub fn merge_config<P: AsRef<Path> + ?Sized>(&mut self, config_path: &P) -> Result<()> {
38 match std::fs::canonicalize(&config_path) {
39 Ok(p) => {
40 if !self.config_files.insert(std::path::PathBuf::from(p)) {
41 return Ok(());
42 }
43 }
44 Err(err) => return Err(Error::IoError(err)),
45 }
46
47 let config = fs::read_to_string(config_path.as_ref())?;
48 let xml_doc = roxmltree::Document::parse_with_options(
49 &config,
50 roxmltree::ParsingOptions {
51 allow_dtd: true,
52 ..Default::default()
53 },
54 )?;
55
56 for part in parse_config(&xml_doc)? {
57 match part? {
58 ConfigPart::Alias(alias) => self.aliases.push(alias),
59 ConfigPart::Config(mut c) => {
60 self.config.rescans.append(&mut c.rescans);
61 self.config.blanks.append(&mut c.blanks);
62 }
63 ConfigPart::Description(_) => {}
64 ConfigPart::Dir(dir) => self.dirs.push(DirData {
65 path: dir.calculate_path(config_path),
66 salt: dir.salt,
67 }),
68 ConfigPart::CacheDir(dir) => self.cache_dirs.push(dir.calculate_path(config_path)),
69 ConfigPart::Match(m) => self.matches.push(m),
70 ConfigPart::ResetDirs => self.dirs.clear(),
71 ConfigPart::SelectFont(s) => self.select_fonts.push(s),
72 ConfigPart::RemapDir(remap) => self.remap_dirs.push(RemapDirData {
73 path: remap.calculate_path(config_path),
74 salt: remap.salt,
75 as_path: remap.as_path,
76 }),
77 ConfigPart::Include(dir) => {
78 let include_path = dir.calculate_path(config_path);
79
80 match self.include(&include_path) {
81 Ok(_) => {}
82 #[allow(unused_variables)]
83 Err(err) => {
84 if !dir.ignore_missing {
85 #[cfg(feature = "log")]
86 log::warn!("Failed to include {}: {}", include_path.display(), err);
87 }
88 }
89 }
90 }
91 }
92 }
93
94 Ok(())
95 }
96
97 fn include(&mut self, include_path: &Path) -> Result<()> {
98 let meta = fs::metadata(include_path)?;
99 let ty = meta.file_type();
100
101 // fs::metadata follow symlink so ty is never symlink
102 if ty.is_file() {
103 self.merge_config(include_path)?;
104 } else if ty.is_dir() {
105 let dir = std::fs::read_dir(include_path)?;
106 let config_paths = dir
107 .filter_map(|entry| {
108 let entry = entry.ok()?;
109 let ty = entry.file_type().ok()?;
110
111 if ty.is_file() || ty.is_symlink() {
112 Some(entry.path())
113 } else {
114 None
115 }
116 })
117 .collect::<BinaryHeap<_>>();
118
119 for config_path in config_paths {
120 match self.merge_config(&config_path) {
121 Ok(_) => {}
122 #[allow(unused_variables)]
123 Err(err) => {
124 #[cfg(feature = "log")]
125 log::warn!("Failed to merge {}: {}", config_path.display(), err);
126 }
127 }
128 }
129 }
130
131 Ok(())
132 }
133}
134
135macro_rules! define_config_part_from {
136 ($($f:ident,)+) => {
137 $(
138 impl From<$f> for ConfigPart {
139 fn from(v: $f) -> Self {
140 ConfigPart::$f(v)
141 }
142 }
143 )+
144 };
145}
146
147define_config_part_from! {
148 SelectFont,
149 Dir,
150 CacheDir,
151 Include,
152 Match,
153 Config,
154 Alias,
155 RemapDir,
156}
157
158#[derive(Clone, Debug, Default, PartialEq, Eq)]
159#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
160/// Final dir data
161pub struct DirData {
162 /// dir path
163 pub path: PathBuf,
164 /// 'salt' property affects to determine cache filename. this is useful for example when having different fonts sets on same path at container and share fonts from host on different font path.
165 pub salt: String,
166}
167
168#[derive(Clone, Debug, Default, PartialEq, Eq)]
169#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
170/// Final remap-dirs data
171pub struct RemapDirData {
172 /// dir path will be mapped as the path [`as-path`](Self::as_path) in cached information. This is useful if the directory name is an alias (via a bind mount or symlink) to another directory in the system for which cached font information is likely to exist.
173 pub path: PathBuf,
174 /// 'salt' property affects to determine cache filename. this is useful for example when having different fonts sets on same path at container and share fonts from host on different font path.
175 pub salt: String,
176 // remapped path
177 pub as_path: String,
178}
179