Compare commits

..

No commits in common. "8618e563dc68da4cc293939db004abfb97d7d21d" and "bc54a2d418c3053a6549ad15d871a4c223ccb9a8" have entirely different histories.

4 changed files with 80 additions and 268 deletions

View File

@ -72,12 +72,7 @@ pub enum Action {
} }
impl Action { impl Action {
pub fn rename<S>(new_name: S) -> Self pub fn rename<S>(new_name: S) -> Self where S: AsRef<OsStr> {
where Self::Rename {new_name: new_name.as_ref().to_owned()}
S: AsRef<OsStr>,
{
Self::Rename {
new_name: new_name.as_ref().to_owned(),
}
} }
} }

View File

@ -1,32 +1,17 @@
use crate::os_str_extension::OsStrExtension;
use crate::osf;
use std::error::Error; use std::error::Error;
use std::ffi::{OsStr, OsString};
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::os::unix::ffi::OsStrExt;
use std::path::PathBuf; use std::path::PathBuf;
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct FileNameInfo { pub struct FileNameInfo {
pub name: OsString, pub name: String,
pub version: Option<OsString>, pub version: Option<String>,
pub extension: Option<OsString>, pub extension: Option<String>,
} }
impl FileNameInfo { impl FileNameInfo {
pub fn to_full_file_name(&self) -> OsString { pub fn to_full_file_name(&self) -> String {
(osf!(&self.name) self.to_string()
+ self
.version
.as_ref()
.map(|v| osf!("-") + v)
.unwrap_or_default()
+ self
.extension
.as_ref()
.map(|extension| osf!(".") + extension)
.unwrap_or_default())
.build()
} }
} }
@ -35,27 +20,25 @@ impl TryFrom<PathBuf> for FileNameInfo {
fn try_from(file: PathBuf) -> Result<Self, Self::Error> { fn try_from(file: PathBuf) -> Result<Self, Self::Error> {
let file_name = file.file_name().ok_or(FileInfoError::NotAFile)?; let file_name = file.file_name().ok_or(FileInfoError::NotAFile)?;
let file_name = file_name
.to_str()
.ok_or(FileInfoError::InvalidCharactersInFileName)?;
let (file_name_without_extension, extension) = let (file_name_without_version, extension) =
match file_name.rsplit_once(b'.').filter(|(_, ending)| { match file_name.rsplit_once('.').filter(|(_, ending)| {
//there are usually no file extensions which are just a number, but rather versions ending.parse::<u32>().is_err() //there are usually no file extensions which are just a number, but rather versions
// -> don't use split if ending is number
match ending.to_str() {
Some(ending) if ending.parse::<u32>().is_ok() => false,
_ => true,
}
}) { }) {
Some((name, ending)) => (name, Some(ending.to_os_string())), Some((name, ending)) => (name, Some(ending.to_string())),
None => (file_name, None), None => (file_name, None),
}; };
let (name, version) = match file_name_without_extension.split_once(b'-') { let (name, version) = match file_name_without_version.split_once('-') {
Some((name, version)) => (name, Some(version.to_os_string())), Some((name, version)) => (name, Some(version.to_string())),
None => (file_name_without_extension, None), None => (file_name_without_version, None),
}; };
Ok(Self { Ok(Self {
name: name.to_os_string(), name: name.to_string(),
version, version,
extension, extension,
}) })
@ -66,8 +49,18 @@ impl Display for FileNameInfo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,
"{}", "{}{}{}",
self.to_full_file_name().to_string_lossy() self.name,
self
.version
.as_ref()
.map(|v| format!("-{v}"))
.unwrap_or_default(),
self
.extension
.as_ref()
.map(|extension| format!(".{extension}"))
.unwrap_or_default()
) )
} }
} }
@ -91,41 +84,37 @@ impl Error for FileInfoError {}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FileMatcher { pub struct FileMatcher {
name: OsString, name: String,
extension: Option<OsString>, extension: Option<String>,
} }
impl FileMatcher { impl FileMatcher {
pub fn from<S>(name: S) -> Self pub fn from<S>(name: S) -> Self
where where
S: AsRef<OsStr>, S: ToString,
{ {
Self { Self {
name: name.as_ref().to_owned(), name: name.to_string(),
extension: None, extension: None,
} }
} }
pub fn and_extension<S>(self, extension: S) -> Self pub fn and_extension<S>(self, extension: S) -> Self
where where
S: AsRef<OsStr>, S: ToString,
{ {
Self { Self {
extension: Some(extension.as_ref().to_owned()), extension: Some(extension.to_string()),
..self ..self
} }
} }
pub fn matches<S>(&self, file_name: S) -> bool pub fn matches(&self, file_name: &str) -> bool {
where file_name.starts_with(&self.name)
S: AsRef<OsStr>,
{
let file_name = file_name.as_ref();
file_name.as_bytes().starts_with(self.name.as_bytes())
&& self && self
.extension .extension
.as_ref() .as_ref()
.is_none_or(|extension| file_name.as_bytes().ends_with(extension.as_bytes())) .is_none_or(|extension| file_name.ends_with(extension))
} }
} }
@ -138,9 +127,9 @@ mod test_file_name_info {
fn test_from_plugin() { fn test_from_plugin() {
assert_eq!( assert_eq!(
FileNameInfo { FileNameInfo {
name: "TestPlugin".into(), name: "TestPlugin".to_string(),
version: Some("1.0.0".into()), version: Some("1.0.0".to_string()),
extension: Some("jar".into()), extension: Some("jar".to_string()),
}, },
FileNameInfo::try_from(PathBuf::from("test-ressources/files/TestPlugin-1.0.0.jar")) FileNameInfo::try_from(PathBuf::from("test-ressources/files/TestPlugin-1.0.0.jar"))
.expect("valid file") .expect("valid file")
@ -151,9 +140,9 @@ mod test_file_name_info {
fn test_from_unversioned() { fn test_from_unversioned() {
assert_eq!( assert_eq!(
FileNameInfo { FileNameInfo {
name: "unversioned".into(), name: "unversioned".to_string(),
version: None, version: None,
extension: Some("jar".into()), extension: Some("jar".to_string()),
}, },
FileNameInfo::try_from(PathBuf::from("test-ressources/files/unversioned.jar")) FileNameInfo::try_from(PathBuf::from("test-ressources/files/unversioned.jar"))
.expect("valid file") .expect("valid file")
@ -164,8 +153,8 @@ mod test_file_name_info {
fn test_from_versioned_bin() { fn test_from_versioned_bin() {
assert_eq!( assert_eq!(
FileNameInfo { FileNameInfo {
name: "bin".into(), name: "bin".to_string(),
version: Some("7.3".into()), version: Some("7.3".to_string()),
extension: None, extension: None,
}, },
FileNameInfo::try_from(PathBuf::from("test-ressources/files/bin-7.3")).expect("valid file") FileNameInfo::try_from(PathBuf::from("test-ressources/files/bin-7.3")).expect("valid file")
@ -176,7 +165,7 @@ mod test_file_name_info {
fn test_from_unversioned_bin() { fn test_from_unversioned_bin() {
assert_eq!( assert_eq!(
FileNameInfo { FileNameInfo {
name: "test".into(), name: "test".to_string(),
version: None, version: None,
extension: None, extension: None,
}, },

View File

@ -3,24 +3,23 @@ mod command;
mod file; mod file;
mod logger; mod logger;
mod os_string_builder; mod os_string_builder;
mod os_str_extension;
mod server; mod server;
use crate::action::{Action, FileAction, ServerActions}; use crate::action::{Action, FileAction, ServerActions};
use crate::command::{CommandSpecificError, ExecutionError, LogRunnable}; use crate::command::{CommandSpecificError, ExecutionError, LogRunnable};
use crate::file::{FileMatcher, FileNameInfo}; use crate::file::{FileMatcher, FileNameInfo};
use crate::logger::{LogLevel, Logger}; use crate::logger::{LogLevel, Logger};
use crate::os_str_extension::OsStrExtension;
use crate::os_string_builder::ReplaceWithOsStr; use crate::os_string_builder::ReplaceWithOsStr;
use crate::server::{RelativeLocalPathAnker, ServerAddress}; use crate::server::{RelativeLocalPathAnker, ServerAddress};
use clap::{Parser, Subcommand, ValueEnum}; use clap::{Parser, Subcommand, ValueEnum};
use lazy_regex::{lazy_regex, Lazy, Regex}; use lazy_regex::{lazy_regex, Lazy, Regex};
use server::{Server, ServerReference}; use server::{Server, ServerReference};
use std::cell::LazyCell; use std::cell::LazyCell;
use std::ffi::OsString; use std::ffi::OsStr;
use std::hash::Hash; use std::hash::Hash;
use std::io::Write; use std::io::Write;
use std::iter::once; use std::iter::once;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr; use std::str::FromStr;
use std::{env, fs, io}; use std::{env, fs, io};
@ -233,11 +232,11 @@ fn main() -> Result<(), String> {
))?; ))?;
} }
let denoted_files = osstring_from_ssh_output(output.stdout) let denoted_files = output
.split(b'\n') //split at line breaks .stdout
.into_iter() .split(|&b| b == b'\n') //split at line breaks
.filter(|bytes| !bytes.is_empty()) //needed since realpath sometimes gives us empty lines .filter(|bytes| !bytes.is_empty()) //needed since realpath sometimes gives us empty lines
.map(PathBuf::from) .map(|bytes| PathBuf::from(OsStr::from_bytes(bytes)))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
Ok(denoted_files) Ok(denoted_files)
@ -293,48 +292,28 @@ fn main() -> Result<(), String> {
Ok(ServerActions { Ok(ServerActions {
server, server,
actions: { actions: {
let present_file_names: Vec<OsString> = match &server.address { let mut ls_command = match &server.address {
ServerAddress::Ssh { ssh_address } => osstring_from_ssh_output( ServerAddress::Ssh { ssh_address } => {
ShellCmd::new("ls") let mut cmd = ShellCmd::new("ssh");
.arg(ssh_address) cmd.arg(ssh_address).arg(osf!("ls ") + &working_directory);
.arg(osf!("ls ") + &working_directory) cmd
.collect_output() }
.map_err(|e| { ServerAddress::Localhost => {
format!( let mut cmd = ShellCmd::new("ls");
"Failed to query present files on server {}: {e}", cmd.arg(&working_directory);
server.get_name() cmd
) }
})?
.stdout,
)
.split(b'\n')
.into_iter()
.map(OsString::from)
.collect(),
ServerAddress::Localhost => fs::read_dir(&working_directory)
.map_err(|e| format!("Failed to get files in working directory: {e}"))?
.map(|entry| entry.map_err(|e| format!("Failed to access directory entry: {e}")))
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.filter_map(|entry| {
if entry.path().is_file() {
Some(entry.file_name())
} else {
None
}
})
.collect(),
}; };
let ls_output = ls_command
.collect_output()
.map_err(|e| format!("failed to query files: {e}"))?;
let ls_output = String::from_utf8_lossy(&ls_output.stdout);
file_details file_details
.iter() .iter()
.flat_map(|(file, file_name_info)| { .flat_map(|(file, file_name_info)| {
let mut file_matcher = FileMatcher::from( let mut file_matcher =
file_name FileMatcher::from(file_name.as_ref().unwrap_or(&file_name_info.name));
.as_ref()
.map(OsString::from)
.unwrap_or(file_name_info.name.to_os_string()),
);
if let Some(extension) = file_name_info.extension.as_ref() { if let Some(extension) = file_name_info.extension.as_ref() {
file_matcher = file_matcher.and_extension(extension); file_matcher = file_matcher.and_extension(extension);
} }
@ -343,53 +322,39 @@ fn main() -> Result<(), String> {
let add_action = FileAction::new(file, Action::Add).expect("path points to file"); let add_action = FileAction::new(file, Action::Add).expect("path points to file");
if pure && present_file_names.iter().any(|file| *file == file_name) { let mut ls_lines = ls_output.lines();
log!(
logger, if pure && ls_lines.clone().any(|file| file == file_name) {
debug, log!(logger, debug, "file is already present on {}: {}", server.get_name(), file_name);
"file is already present on {}: {}",
server.get_name(),
file_name.to_string_lossy()
);
return vec![]; //ignore that file, since it is already present return vec![]; //ignore that file, since it is already present
} }
match old_version_policy { match old_version_policy {
OldVersionPolicy::Ignore => { OldVersionPolicy::Ignore => {
if !present_file_names.iter().any(|file| *file == file_name) { if !ls_lines.any(|file| file == file_name) {
vec![add_action] //file doesn't exist yet vec![add_action] //file doesn't exist yet
} else { } else {
vec![FileAction::new(&file_name, Action::Replace) vec![FileAction::new(&file_name, Action::Replace)
.expect("path points to file")] .expect("path points to file")]
} }
} }
OldVersionPolicy::Archive => present_file_names OldVersionPolicy::Archive => ls_lines
.iter()
.filter(|file| file_matcher.matches(file)) .filter(|file| file_matcher.matches(file))
.map(|file| { .map(|file| {
FileAction::new( FileAction::new(
file, file,
Action::rename( Action::rename(format!("{file}{}", file.chars().last().unwrap_or('1'))),
osf!(file)
+ file
.to_string_lossy()
.chars()
.last()
.unwrap_or('1')
.to_string(),
),
) )
.expect("path points to file") .expect("path points to file")
}) })
.chain(once(add_action)) .chain(once(add_action))
.collect(), .collect(),
OldVersionPolicy::Delete => { OldVersionPolicy::Delete => {
let mut actions = present_file_names let mut actions = ls_lines
.iter()
.filter(|file| file_matcher.matches(file)) .filter(|file| file_matcher.matches(file))
.map(|file| { .map(|file| {
//special case -> file has the same name as current file, then we just need to replace it //special case -> file has the same name as current file, then we just need to replace it
if *file == file_name { if file == file_name {
FileAction::new(file, Action::Replace).expect("path points to file") FileAction::new(file, Action::Replace).expect("path points to file")
} else { } else {
FileAction::new(file, Action::Delete).expect("path points to file") FileAction::new(file, Action::Delete).expect("path points to file")
@ -624,20 +589,6 @@ fn main() -> Result<(), String> {
Ok(()) Ok(())
} }
fn osstring_from_ssh_output(output: Vec<u8>) -> OsString {
#[cfg(unix)]
{
use std::os::unix::ffi::OsStringExt;
OsString::from_vec(output)
}
#[cfg(windows)]
{
use std::os::windows::ffi::OsStringExt;
OsString::from_wide(output.iter().map(|&b| b as u16).collect())
}
}
fn check_local_file_exists<P>(path: P) -> Result<(), String> fn check_local_file_exists<P>(path: P) -> Result<(), String>
where where
P: AsRef<Path>, P: AsRef<Path>,

View File

@ -1,123 +0,0 @@
use std::ffi::OsStr;
pub trait OsStrExtension {
fn starts_with<S>(&self, prefix: S) -> bool
where
S: AsRef<OsStr>;
fn ends_with<S>(&self, suffix: S) -> bool
where
S: AsRef<OsStr>;
fn split(&self, separator: u8) -> Vec<&OsStr>;
fn splitn(&self, n: usize, separator: u8) -> Vec<&OsStr>;
fn split_once(&self, separator: u8) -> Option<(&OsStr, &OsStr)>;
fn rsplitn(&self, n: usize, separator: u8) -> Vec<&OsStr>;
fn rsplit_once(&self, separator: u8) -> Option<(&OsStr, &OsStr)>;
}
impl<T> OsStrExtension for T
where
T: AsRef<OsStr>,
{
fn starts_with<S>(&self, prefix: S) -> bool
where
S: AsRef<OsStr>,
{
self
.as_ref()
.as_encoded_bytes()
.starts_with(prefix.as_ref().as_encoded_bytes())
}
fn ends_with<S>(&self, suffix: S) -> bool
where
S: AsRef<OsStr>,
{
self
.as_ref()
.as_encoded_bytes()
.ends_with(suffix.as_ref().as_encoded_bytes())
}
fn split(&self, separator: u8) -> Vec<&OsStr> {
self
.as_ref()
.as_encoded_bytes()
.split(|&b| b == separator)
.map(|bytes| unsafe { OsStr::from_encoded_bytes_unchecked(bytes) })
.collect()
}
fn splitn(&self, n: usize, separator: u8) -> Vec<&OsStr> {
self
.as_ref()
.as_encoded_bytes()
.splitn(n, |&b| b == separator)
.map(|bytes| unsafe { OsStr::from_encoded_bytes_unchecked(bytes) })
.collect()
}
fn split_once(&self, separator: u8) -> Option<(&OsStr, &OsStr)> {
let mut iter = self.splitn(2, separator).into_iter();
Some((iter.next()?, iter.next()?))
}
fn rsplitn(&self, n: usize, separator: u8) -> Vec<&OsStr> {
self
.as_ref()
.as_encoded_bytes()
.rsplitn(n, |&b| b == separator)
.map(|bytes| unsafe { OsStr::from_encoded_bytes_unchecked(bytes) })
.collect::<Vec<_>>()
.into_iter()
.rev()
.collect()
}
fn rsplit_once(&self, separator: u8) -> Option<(&OsStr, &OsStr)> {
let mut iter = self.rsplitn(2, separator).into_iter();
Some((iter.next()?, iter.next()?))
}
}
#[cfg(test)]
mod test {
use crate::os_str_extension::OsStrExtension;
use std::ffi::OsString;
#[test]
fn test_starts_with() {
assert!(OsString::from("Hello world").starts_with("Hell"));
assert!(!OsString::from("Hello world").starts_with("world"));
}
#[test]
fn test_ends_with() {
assert!(OsString::from("Hello world").ends_with("ld"));
assert!(!OsString::from("Hello world").ends_with("Hello"));
}
#[test]
fn test_split() {
assert_eq!(vec![OsString::from("Hello"), OsString::from("world")], OsString::from("Hello world").split(b' '));
}
#[test]
fn test_splitn() {
assert_eq!(vec![OsString::from("a"), OsString::from("b"), OsString::from("c d")], OsString::from("a b c d").splitn(3, b' '));
}
#[test]
fn test_split_once() {
assert_eq!(Some((OsString::from("a").as_ref(), OsString::from("b c").as_ref())), OsString::from("a b c").split_once(b' '));
}
#[test]
fn test_rsplitn() {
assert_eq!(vec![OsString::from("a b"), OsString::from("c"), OsString::from("d")], OsString::from("a b c d").rsplitn(3, b' '));
}
#[test]
fn test_rsplit_once() {
assert_eq!(Some((OsString::from("a b").as_ref(), OsString::from("c").as_ref())), OsString::from("a b c").rsplit_once(b' '));
}
}