use std::sync::Arc;
use super::ChangeSet;
use crate::config::Project;
use crate::ext::anyhow::{Context, Result};
use crate::service::notify::Watched;
use crate::service::site::SourcedSiteFile;
use crate::signal::{Outcome, Product};
use crate::{ext::PathExt, fs, logger::GRAY};
use camino::{Utf8Path, Utf8PathBuf};
use tokio::task::JoinHandle;
pub async fn assets(
proj: &Arc<Project>,
changes: &ChangeSet,
first_sync: bool,
) -> JoinHandle<Result<Outcome<Product>>> {
let changes = changes.clone();
let proj = proj.clone();
tokio::spawn(async move {
let Some(assets) = &proj.assets else {
return Ok(Outcome::Success(Product::None));
};
let dest_root = &proj.site.root_dir;
let pkg_dir = &proj.site.pkg_dir;
let change = if first_sync {
log::trace!("Assets starting full resync");
resync(&assets.dir, dest_root, pkg_dir).await?;
true
} else {
let mut changed = false;
for watched in changes.asset_iter() {
log::trace!("Assets processing {watched:?}");
let change =
update_asset(&proj, watched.clone(), &assets.dir, dest_root, pkg_dir, &[])
.await?;
changed |= change;
}
changed
};
if change {
log::debug!("Assets finished (with changes)");
Ok(Outcome::Success(Product::Assets))
} else {
log::debug!("Assets finished (no changes)");
Ok(Outcome::Success(Product::None))
}
})
}
async fn update_asset(
proj: &Project,
watched: Watched,
src_root: &Utf8Path,
dest_root: &Utf8Path,
pkg_dir: &Utf8Path,
reserved: &[Utf8PathBuf],
) -> Result<bool> {
if let Some(path) = watched.path() {
if reserved.contains(path) {
log::warn!("Assets reserved filename for Leptos. Please remove {path:?}");
return Ok(false);
}
}
Ok(match watched {
Watched::Create(f) => {
let to = f.rebase(src_root, dest_root)?;
if f.is_dir() {
fs::copy_dir_all(f, to).await?;
} else {
fs::copy(&f, &to).await?;
}
true
}
Watched::Remove(f) => {
let path = f.rebase(src_root, dest_root)?;
if path.is_dir() {
fs::remove_dir_all(&path)
.await
.context(format!("remove dir recursively {path:?}"))?;
} else {
fs::remove_file(&path)
.await
.context(format!("remove file {path:?}"))?;
}
false
}
Watched::Rename(from, to) => {
let from = from.rebase(src_root, dest_root)?;
let to = to.rebase(src_root, dest_root)?;
fs::rename(&from, &to)
.await
.context(format!("rename {from:?} to {to:?}"))?;
true
}
Watched::Write(f) => {
let file = SourcedSiteFile {
source: f.clone(),
dest: f.rebase(src_root, dest_root)?,
site: f.unbase(src_root)?,
};
proj.site.updated(&file).await?
}
Watched::Rescan => {
resync(src_root, dest_root, pkg_dir).await?;
true
}
})
}
pub fn reserved(src: &Utf8Path, pkg_dir: &Utf8Path) -> Vec<Utf8PathBuf> {
vec![src.join("index.html"), pkg_dir.to_path_buf()]
}
async fn resync(src: &Utf8Path, dest: &Utf8Path, pkg_dir: &Utf8Path) -> Result<()> {
clean_dest(dest, pkg_dir)
.await
.context(format!("Cleaning {dest:?}"))?;
let reserved = reserved(src, pkg_dir);
mirror(src, dest, &reserved)
.await
.context(format!("Mirroring {src:?} -> {dest:?}"))
}
async fn clean_dest(dest: &Utf8Path, pkg_dir: &Utf8Path) -> Result<()> {
let pkg_dir_name = match pkg_dir.file_name() {
Some(name) => name,
None => {
log::warn!(
"Assets No site-pkg-dir given, defaulting to 'pkg' for checks what to delete."
);
log::warn!("Assets This will probably delete already generated files.");
"pkg"
}
};
let mut entries = fs::read_dir(dest).await?;
while let Some(entry) = entries.next_entry().await? {
let path = entry.path();
if entry.file_type().await?.is_dir() {
if entry.file_name() != pkg_dir_name {
log::debug!(
"Assets removing folder {}",
GRAY.paint(path.to_string_lossy())
);
fs::remove_dir_all(path).await?;
}
} else if entry.file_name() != "index.html" {
log::debug!(
"Assets removing file {}",
GRAY.paint(path.to_string_lossy())
);
fs::remove_file(path).await?;
}
}
Ok(())
}
async fn mirror(src_root: &Utf8Path, dest_root: &Utf8Path, reserved: &[Utf8PathBuf]) -> Result<()> {
let mut entries = src_root.read_dir_utf8()?;
while let Some(Ok(entry)) = entries.next() {
let from = entry.path().to_path_buf();
let to = from.rebase(src_root, dest_root)?;
if reserved.contains(&from) {
log::warn!("");
continue;
}
if entry.file_type()?.is_dir() {
log::debug!(
"Assets copy folder {} -> {}",
GRAY.paint(from.as_str()),
GRAY.paint(to.as_str())
);
fs::copy_dir_all(from, to).await?;
} else {
log::debug!(
"Assets copy file {} -> {}",
GRAY.paint(from.as_str()),
GRAY.paint(to.as_str())
);
fs::copy(from, to).await?;
}
}
Ok(())
}