glory_cli/service/
site.rs

1use 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    /// source file's relative path from the root (workspace or project) directory
21    pub source: Utf8PathBuf,
22    /// dest file's relative path from the root (workspace or project) directory
23    pub dest: Utf8PathBuf,
24    /// dest file's relative path from the site directory
25    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    /// dest file's relative path from the root (workspace or project) directory
56    pub dest: Utf8PathBuf,
57    /// dest file's relative path from the site directory
58    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    /// check if the file changed
116    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    /// check after writing the file if it changed
145    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}