Update all modules to use registry - part 3
This commit is contained in:
212
crates/common/src/manager/application.rs
Normal file
212
crates/common/src/manager/application.rs
Normal file
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::WEBADMIN_KEY;
|
||||
use crate::Core;
|
||||
use ahash::AHashMap;
|
||||
use arc_swap::ArcSwap;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
io::{self, Cursor, Read},
|
||||
path::PathBuf,
|
||||
};
|
||||
use store::BlobStore;
|
||||
|
||||
pub struct WebApplicationManager {
|
||||
bundle_path: TempDir,
|
||||
routes: ArcSwap<AHashMap<String, Resource<PathBuf>>>,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct Resource<T> {
|
||||
pub content_type: Cow<'static, str>,
|
||||
pub contents: T,
|
||||
}
|
||||
|
||||
impl<T> Resource<T> {
|
||||
pub fn new(content_type: impl Into<Cow<'static, str>>, contents: T) -> Self {
|
||||
Self {
|
||||
content_type: content_type.into(),
|
||||
contents,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WebApplicationManager {
|
||||
pub fn new(base_path: PathBuf) -> Self {
|
||||
Self {
|
||||
bundle_path: TempDir::new(base_path),
|
||||
routes: ArcSwap::from_pointee(Default::default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get(&self, path: &str) -> trc::Result<Resource<Vec<u8>>> {
|
||||
let routes = self.routes.load();
|
||||
if let Some(resource) = routes.get(path).or_else(|| routes.get("index.html")) {
|
||||
tokio::fs::read(&resource.contents)
|
||||
.await
|
||||
.map(|contents| Resource {
|
||||
content_type: resource.content_type.clone(),
|
||||
contents,
|
||||
})
|
||||
.map_err(|err| {
|
||||
trc::ResourceEvent::Error
|
||||
.reason(err)
|
||||
.ctx(trc::Key::Path, path.to_string())
|
||||
.caused_by(trc::location!())
|
||||
})
|
||||
} else {
|
||||
Ok(Resource::default())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn unpack(&self, blob_store: &BlobStore) -> trc::Result<()> {
|
||||
// Delete any existing bundles
|
||||
self.bundle_path.clean().await.map_err(unpack_error)?;
|
||||
|
||||
// Obtain webadmin bundle
|
||||
let bundle = blob_store
|
||||
.get_blob(WEBADMIN_KEY, 0..usize::MAX)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
trc::ResourceEvent::NotFound
|
||||
.caused_by(trc::location!())
|
||||
.details("Webadmin bundle not found")
|
||||
})?;
|
||||
|
||||
// Uncompress
|
||||
let mut bundle = zip::ZipArchive::new(Cursor::new(bundle)).map_err(|err| {
|
||||
trc::ResourceEvent::Error
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
.details("Failed to decompress webadmin bundle")
|
||||
})?;
|
||||
let mut routes = AHashMap::new();
|
||||
for i in 0..bundle.len() {
|
||||
let (file_name, contents) = {
|
||||
let mut file = bundle.by_index(i).map_err(|err| {
|
||||
trc::ResourceEvent::Error
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
.details("Failed to read file from webadmin bundle")
|
||||
})?;
|
||||
if file.is_dir() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut contents = Vec::new();
|
||||
file.read_to_end(&mut contents).map_err(unpack_error)?;
|
||||
(file.name().to_string(), contents)
|
||||
};
|
||||
let path = self.bundle_path.path.join(format!("{i:02}"));
|
||||
tokio::fs::write(&path, contents)
|
||||
.await
|
||||
.map_err(unpack_error)?;
|
||||
|
||||
let resource = Resource {
|
||||
content_type: match file_name
|
||||
.rsplit_once('.')
|
||||
.map(|(_, ext)| ext)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
"html" => "text/html",
|
||||
"css" => "text/css",
|
||||
"wasm" => "application/wasm",
|
||||
"js" => "application/javascript",
|
||||
"json" => "application/json",
|
||||
"png" => "image/png",
|
||||
"svg" => "image/svg+xml",
|
||||
"ico" => "image/x-icon",
|
||||
_ => "application/octet-stream",
|
||||
}
|
||||
.into(),
|
||||
contents: path,
|
||||
};
|
||||
|
||||
routes.insert(file_name, resource);
|
||||
}
|
||||
|
||||
// Update routes
|
||||
self.routes.store(routes.into());
|
||||
|
||||
trc::event!(
|
||||
Resource(trc::ResourceEvent::WebadminUnpacked),
|
||||
Path = self.bundle_path.path.to_string_lossy().into_owned(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update(&self, core: &Core) -> trc::Result<()> {
|
||||
todo!()
|
||||
/* let bytes = core
|
||||
.storage
|
||||
.config
|
||||
.fetch_resource("webadmin")
|
||||
.await
|
||||
.map_err(|err| {
|
||||
trc::ResourceEvent::Error
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
.details("Failed to download webadmin")
|
||||
})?;
|
||||
core.storage.blob.put_blob(WEBADMIN_KEY, &bytes).await*/
|
||||
}
|
||||
|
||||
pub async fn update_and_unpack(&self, core: &Core) -> trc::Result<()> {
|
||||
self.update(core).await?;
|
||||
self.unpack(&core.storage.blob).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resource<Vec<u8>> {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.content_type.is_empty() && self.contents.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TempDir {
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
impl TempDir {
|
||||
pub fn new(path: PathBuf) -> TempDir {
|
||||
TempDir {
|
||||
path: path.join(std::str::from_utf8(WEBADMIN_KEY).unwrap()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn clean(&self) -> io::Result<()> {
|
||||
if tokio::fs::metadata(&self.path).await.is_ok() {
|
||||
let _ = tokio::fs::remove_dir_all(&self.path).await;
|
||||
}
|
||||
tokio::fs::create_dir(&self.path).await
|
||||
}
|
||||
}
|
||||
|
||||
fn unpack_error(err: std::io::Error) -> trc::Error {
|
||||
trc::ResourceEvent::Error
|
||||
.reason(err)
|
||||
.details("Failed to unpack webadmin bundle")
|
||||
}
|
||||
|
||||
impl Default for WebApplicationManager {
|
||||
fn default() -> Self {
|
||||
Self::new(std::env::temp_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TempDir {
|
||||
fn default() -> Self {
|
||||
Self::new(std::env::temp_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TempDir {
|
||||
fn drop(&mut self) {
|
||||
let _ = std::fs::remove_dir_all(&self.path);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user