use anyhow::{Context, Result};
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile;
use tracing::{debug, info};
pub struct FileService {
pub data_dir: PathBuf,
}
impl FileService {
pub fn new(data_dir: PathBuf) -> Self {
Self { data_dir }
}
pub fn extract_bundle(&self, bundle_path: &Path, temp_dir: &Path) -> Result<PathBuf> {
info!("Extracting release bundle...");
let status = Command::new("tar")
.args(["xzf", bundle_path.to_str().unwrap()])
.current_dir(temp_dir)
.output()
.context("Failed to execute tar command")?;
if !status.status.success() {
let error = String::from_utf8_lossy(&status.stderr);
return Err(anyhow::anyhow!(
"Failed to extract release bundle: {}",
error
));
}
Ok(temp_dir.join("release_bundle"))
}
pub fn extract_bundle_with_details(
&self,
bundle_path: &Path,
temp_dir: &Path,
) -> Result<PathBuf> {
info!("Extracting release bundle...");
let release_bundle_dir = temp_dir.join("release_bundle");
fs::create_dir_all(&release_bundle_dir)?;
if bundle_path.exists() {
let metadata = fs::metadata(bundle_path)?;
debug!("Bundle file exists, size: {} bytes", metadata.len());
} else {
return Err(anyhow::anyhow!(
"Bundle file does not exist: {}",
bundle_path.display()
));
}
debug!("Listing contents of the tarball:");
let list_output = Command::new("tar").arg("-tvf").arg(bundle_path).output()?;
if list_output.status.success() {
let stdout = String::from_utf8_lossy(&list_output.stdout);
debug!("Tarball contents:\n{}", stdout);
} else {
let stderr = String::from_utf8_lossy(&list_output.stderr);
debug!("Failed to list tarball contents: {}", stderr);
}
debug!("Extracting tarball to: {}", release_bundle_dir.display());
let output = Command::new("tar")
.arg("-xzf")
.arg(bundle_path)
.arg("-C")
.arg(&release_bundle_dir)
.output()?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(anyhow::anyhow!("Failed to extract tarball: {}", stderr));
}
debug!("Contents of release_bundle_dir:");
self.walk_directory(&release_bundle_dir)?;
Ok(release_bundle_dir)
}
fn walk_directory(&self, dir: &Path) -> Result<Vec<PathBuf>> {
let mut files = Vec::new();
if dir.exists() && dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
debug!(" {}", path.display());
files.push(path.clone());
if path.is_dir() {
let subdir_files = FileService::walk_directory_static(path.as_path())?;
files.extend(subdir_files);
}
}
}
Ok(files)
}
fn walk_directory_static(dir: &Path) -> Result<Vec<PathBuf>> {
let mut files = Vec::new();
if dir.exists() && dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
debug!(" {}", path.display());
files.push(path.clone());
if path.is_dir() {
let subdir_files = FileService::walk_directory_static(path.as_path())?;
files.extend(subdir_files);
}
}
}
Ok(files)
}
pub fn copy_dir_all(&self, src: &Path, dst: &Path) -> Result<()> {
fs::create_dir_all(dst)?;
for entry in fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
let src_path = entry.path();
let dst_path = dst.join(entry.file_name());
if ty.is_dir() {
FileService::copy_dir_all_static(&src_path, &dst_path)?;
} else {
fs::copy(&src_path, &dst_path)?;
}
}
Ok(())
}
fn copy_dir_all_static(src: &Path, dst: &Path) -> Result<()> {
fs::create_dir_all(dst)?;
for entry in fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
let src_path = entry.path();
let dst_path = dst.join(entry.file_name());
if ty.is_dir() {
FileService::copy_dir_all_static(&src_path, &dst_path)?;
} else {
fs::copy(&src_path, &dst_path)?;
}
}
Ok(())
}
pub fn install_version(
&self,
release_bundle_dir: &Path,
version: &str,
binary_name: &str,
) -> Result<()> {
let found_files = self.walk_directory(release_bundle_dir)?;
let mut binary_path = None;
let mut manifest_path = None;
for path in &found_files {
let file_name = path.file_name().unwrap_or_default().to_string_lossy();
if file_name == binary_name {
binary_path = Some(path.clone());
} else if file_name == "manifest.yaml" {
manifest_path = Some(path.clone());
}
}
let binary_path = binary_path.unwrap_or_else(|| release_bundle_dir.join(binary_name));
let manifest_path =
manifest_path.unwrap_or_else(|| release_bundle_dir.join("manifest.yaml"));
debug!("Using binary path: {}", binary_path.display());
debug!("Using manifest path: {}", manifest_path.display());
if !binary_path.exists() {
return Err(anyhow::anyhow!(
"Binary path does not exist: {}",
binary_path.display()
));
}
if !manifest_path.exists() {
return Err(anyhow::anyhow!(
"Manifest path does not exist: {}",
manifest_path.display()
));
}
let version_dir = self.data_dir.join(version);
info!("Installing to version directory: {}", version_dir.display());
if version_dir.exists() {
debug!(
"Removing existing version directory: {}",
version_dir.display()
);
fs::remove_dir_all(&version_dir)?;
}
fs::create_dir_all(&version_dir)?;
let dest_binary = version_dir.join(binary_name);
let dest_manifest = version_dir.join("manifest.yaml");
debug!("Copying binary to: {}", dest_binary.display());
fs::copy(&binary_path, &dest_binary)?;
debug!("Copying manifest to: {}", dest_manifest.display());
fs::copy(&manifest_path, &dest_manifest)?;
info!("Successfully installed version: {}", version);
Ok(())
}
pub fn update_files(&self, bundle_path: &Path) -> Result<()> {
info!("Updating application files...");
let temp_dir = tempfile::tempdir()?;
let _release_dir = self.extract_bundle(bundle_path, temp_dir.path())?;
Ok(())
}
pub fn verify_permissions(&self) -> Result<()> {
if !self.data_dir.exists() {
fs::create_dir_all(&self.data_dir).context("Failed to create data directory")?;
}
let test_file = self.data_dir.join(".write_test");
fs::write(&test_file, "test").context("No write permission in data directory")?;
fs::remove_file(test_file).context("Failed to clean up test file")?;
Ok(())
}
}