glory_cli/service/
site.rs1use std::{
2 collections::HashMap,
3 fmt::{self, Display},
4 net::SocketAddr,
5};
6
7use camino::{Utf8Path, Utf8PathBuf};
8use tokio::sync::RwLock;
9
10use crate::{
11 config::ProjectConfig,
12 ext::{
13 anyhow::{Context, Result},
14 fs, PathBufExt,
15 },
16};
17
18#[derive(Clone)]
19pub struct SourcedSiteFile {
20 pub source: Utf8PathBuf,
22 pub dest: Utf8PathBuf,
24 pub site: Utf8PathBuf,
26}
27
28impl SourcedSiteFile {
29 pub fn as_site_file(&self) -> SiteFile {
30 SiteFile {
31 dest: self.dest.clone(),
32 site: self.site.clone(),
33 }
34 }
35}
36
37impl std::fmt::Debug for SourcedSiteFile {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 f.debug_struct("SourcedSiteFile")
40 .field("source", &self.source.test_string())
41 .field("dest", &self.dest.test_string())
42 .field("site", &self.site.test_string())
43 .finish()
44 }
45}
46
47impl Display for SourcedSiteFile {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 write!(f, "{} -> @{}", self.source, self.site)
50 }
51}
52
53#[derive(Clone)]
54pub struct SiteFile {
55 pub dest: Utf8PathBuf,
57 pub site: Utf8PathBuf,
59}
60
61impl Display for SiteFile {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 write!(f, "@{}", self.site)
64 }
65}
66
67impl std::fmt::Debug for SiteFile {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 f.debug_struct("SiteFile")
70 .field("dest", &self.dest.test_string())
71 .field("site", &self.site.test_string())
72 .finish()
73 }
74}
75
76pub struct Site {
77 pub addr: SocketAddr,
78 pub reload: SocketAddr,
79 pub root_dir: Utf8PathBuf,
80 pub pkg_dir: Utf8PathBuf,
81 file_reg: RwLock<HashMap<String, u64>>,
82 ext_file_reg: RwLock<HashMap<String, u64>>,
83}
84
85impl fmt::Debug for Site {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 f.debug_struct("Site")
88 .field("addr", &self.addr)
89 .field("reload", &self.reload)
90 .field("root_dir", &self.root_dir)
91 .field("pkg_dir", &self.pkg_dir)
92 .field("file_reg", &self.file_reg.blocking_read())
93 .field("ext_file_reg", &self.ext_file_reg.blocking_read())
94 .finish()
95 }
96}
97
98impl Site {
99 pub fn new(config: &ProjectConfig) -> Self {
100 let mut reload = config.site_addr;
101 reload.set_port(config.reload_port);
102 Self {
103 addr: config.site_addr,
104 reload,
105 root_dir: config.site_root.clone(),
106 pkg_dir: config.site_pkg_dir.clone(),
107 file_reg: Default::default(),
108 ext_file_reg: Default::default(),
109 }
110 }
111
112 pub fn root_relative_pkg_dir(&self) -> Utf8PathBuf {
113 self.root_dir.join(&self.pkg_dir)
114 }
115 pub async fn did_external_file_change(&self, to: &Utf8Path) -> Result<bool> {
117 let new_hash = file_hash(to).await.dot()?;
118 let cur_hash = { self.ext_file_reg.read().await.get(to.as_str()).copied() };
119 if Some(new_hash) == cur_hash {
120 return Ok(false);
121 }
122 let mut f = self.ext_file_reg.write().await;
123 f.insert(to.to_string(), new_hash);
124 log::trace!("Site update hash for {to} to {new_hash}");
125 Ok(true)
126 }
127
128 pub async fn updated(&self, file: &SourcedSiteFile) -> Result<bool> {
129 fs::create_dir_all(file.dest.clone().without_last()).await?;
130
131 let new_hash = file_hash(&file.source).await?;
132 let cur_hash = self.current_hash(&file.site, &file.dest).await?;
133
134 if Some(new_hash) == cur_hash {
135 return Ok(false);
136 }
137 fs::copy(&file.source, &file.dest).await?;
138
139 let mut reg = self.file_reg.write().await;
140 reg.insert(file.site.to_string(), new_hash);
141 Ok(true)
142 }
143
144 pub async fn did_file_change(&self, file: &SiteFile) -> Result<bool> {
146 let new_hash = file_hash(&file.dest).await.dot()?;
147 let cur_hash = { self.file_reg.read().await.get(file.site.as_str()).copied() };
148 if Some(new_hash) == cur_hash {
149 return Ok(false);
150 }
151 let mut f = self.file_reg.write().await;
152 f.insert(file.site.to_string(), new_hash);
153 Ok(true)
154 }
155
156 pub async fn updated_with(&self, file: &SiteFile, data: &[u8]) -> Result<bool> {
157 fs::create_dir_all(file.dest.clone().without_last()).await?;
158
159 let new_hash = seahash::hash(data);
160 let cur_hash = self.current_hash(&file.site, &file.dest).await?;
161
162 if Some(new_hash) == cur_hash {
163 return Ok(false);
164 }
165
166 fs::write(&file.dest, &data).await?;
167
168 let mut reg = self.file_reg.write().await;
169 reg.insert(file.site.to_string(), new_hash);
170 Ok(true)
171 }
172
173 async fn current_hash(&self, site: &Utf8Path, dest: &Utf8Path) -> Result<Option<u64>> {
174 if let Some(hash) = self.file_reg.read().await.get(site.as_str()).copied() {
175 Ok(Some(hash))
176 } else if dest.exists() {
177 Ok(Some(file_hash(dest).await?))
178 } else {
179 Ok(None)
180 }
181 }
182}
183
184async fn file_hash(file: &Utf8Path) -> Result<u64> {
185 let data = fs::read(&file).await?;
186 Ok(seahash::hash(&data))
187}