use crate::grant_file_access;
use rong::*;
use std::time::SystemTime;
use tokio::fs;
fn file_time_from_millis(millis: f64) -> filetime::FileTime {
let seconds_float = (millis / 1000.0).floor();
let mut seconds = seconds_float as i64;
let mut nanoseconds = ((millis / 1000.0 - seconds_float) * 1_000_000_000.0).round() as u32;
if nanoseconds == 1_000_000_000 && seconds < i64::MAX {
seconds += 1;
nanoseconds = 0;
}
filetime::FileTime::from_unix_time(seconds, nanoseconds)
}
pub(crate) async fn symlink(old_path: String, new_path: String) -> JSResult<()> {
let resolved_old = grant_file_access(&old_path)?;
let resolved_new = grant_file_access(&new_path)?;
#[cfg(unix)]
{
fs::symlink(&resolved_old, &resolved_new)
.await
.map_err(|e| HostError::new("FS_IO", format!("Failed to create symlink: {}", e)).into())
}
#[cfg(windows)]
{
match fs::metadata(&resolved_old).await {
Ok(metadata) => {
if metadata.is_dir() {
tokio::fs::symlink_dir(&resolved_old, &resolved_new).await
} else {
tokio::fs::symlink_file(&resolved_old, &resolved_new).await
}
}
Err(e) => Err(e),
}
.map_err(|e| HostError::new("FS_IO", format!("Failed to create symlink: {}", e)).into())
}
}
pub(crate) async fn readlink(path: String) -> JSResult<String> {
let resolved = grant_file_access(&path)?;
fs::read_link(&resolved)
.await
.map(|p| p.to_string_lossy().into_owned())
.map_err(|e| HostError::new("FS_IO", format!("Failed to read symlink: {}", e)).into())
}
#[cfg(unix)]
pub(crate) async fn chmod(path: String, mode: u32) -> JSResult<()> {
let resolved = grant_file_access(&path)?;
use std::os::unix::fs::PermissionsExt;
let permissions = std::fs::Permissions::from_mode(mode);
fs::set_permissions(&resolved, permissions)
.await
.map_err(|e| HostError::new("FS_IO", format!("Failed to change permissions: {}", e)).into())
}
#[cfg(unix)]
pub(crate) async fn chown(path: String, uid: u32, gid: u32) -> JSResult<()> {
let resolved = grant_file_access(&path)?;
use nix::unistd::{Gid, Uid, chown as nix_chown};
nix_chown(
&resolved,
Some(Uid::from_raw(uid)),
Some(Gid::from_raw(gid)),
)
.map_err(|e| HostError::new("FS_IO", format!("Failed to change ownership: {}", e)).into())
}
#[derive(FromJSObject)]
pub(crate) struct UTimeOptions {
accessed: Option<f64>,
modified: Option<f64>,
}
pub(crate) async fn utime(path: String, options: UTimeOptions) -> JSResult<()> {
let resolved = grant_file_access(&path)?;
use filetime::FileTime;
let atime = options.accessed.map(file_time_from_millis);
let mtime = options.modified.map(file_time_from_millis);
filetime::set_file_times(
&resolved,
atime.unwrap_or_else(|| FileTime::from_system_time(SystemTime::now())),
mtime.unwrap_or_else(|| FileTime::from_system_time(SystemTime::now())),
)
.map_err(|e| HostError::new("FS_IO", format!("Failed to set file times: {}", e)).into())
}
pub(crate) async fn rename(from: String, to: String) -> JSResult<()> {
let resolved_from = grant_file_access(&from)?;
let resolved_to = grant_file_access(&to)?;
fs::rename(&resolved_from, &resolved_to)
.await
.map_err(|e| HostError::new("FS_IO", format!("Failed to rename file: {}", e)).into())
}
pub(crate) async fn real_path(path: String) -> JSResult<String> {
let resolved = grant_file_access(&path)?;
fs::canonicalize(&resolved)
.await
.map(|p| p.to_string_lossy().into_owned())
.map_err(|e| HostError::new("FS_IO", format!("Failed to resolve real path: {}", e)).into())
}