mirror of
https://codeberg.org/postscriptum/snac2nex.git
synced 2026-03-31 21:25:28 +00:00
implement filesystem_file_permissions argument option, add some tests, optimize init arguments
This commit is contained in:
parent
182d6892c3
commit
fabc11f9cb
7 changed files with 152 additions and 142 deletions
|
|
@ -67,6 +67,9 @@ snac2nex -s /path/to/snac/storage -t /path/to/nex -u user1 -u user2
|
||||||
--filesystem-sync
|
--filesystem-sync
|
||||||
Sync filesystem meta (e.g. time modified)
|
Sync filesystem meta (e.g. time modified)
|
||||||
|
|
||||||
|
--filesystem-file-permissions <FILESYSTEM_FILE_PERMISSIONS>
|
||||||
|
Set new file permissions (macos, linux only)
|
||||||
|
|
||||||
-k, --keep
|
-k, --keep
|
||||||
Keep Nex entry on Snac post was removed
|
Keep Nex entry on Snac post was removed
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,15 +52,10 @@ pub struct Config {
|
||||||
#[arg(long, default_value_t = false)]
|
#[arg(long, default_value_t = false)]
|
||||||
pub filesystem_sync: bool,
|
pub filesystem_sync: bool,
|
||||||
|
|
||||||
/* @TODO
|
/// Set new file permissions (macos, linux only)
|
||||||
/// Set directory permissions (macos, linux only)
|
#[arg(long, value_parser = permissions)]
|
||||||
#[arg(long, value_parser = chmod, default_value_t = 0o755)]
|
pub filesystem_file_permissions: Option<u32>,
|
||||||
pub chmod_dir: u32,
|
|
||||||
|
|
||||||
/// Set file permissions (macos, linux only)
|
|
||||||
#[arg(long, value_parser = chmod, default_value_t = 0o644)]
|
|
||||||
pub chmod_file: u32,
|
|
||||||
*/
|
|
||||||
/// Keep Nex entry on Snac post was removed
|
/// Keep Nex entry on Snac post was removed
|
||||||
#[arg(short, long, default_value_t = false)]
|
#[arg(short, long, default_value_t = false)]
|
||||||
pub keep: bool,
|
pub keep: bool,
|
||||||
|
|
@ -70,10 +65,23 @@ pub struct Config {
|
||||||
pub daemon: bool,
|
pub daemon: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @TODO
|
fn permissions(value: &str) -> Result<u32, std::num::ParseIntError> {
|
||||||
fn chmod(chmod: &str) -> Result<u32, std::num::ParseIntError> {
|
if value.len() != 4 {
|
||||||
if chmod.len() != 3 {
|
todo!(
|
||||||
todo!("Expected 3 digits as the Unix value!")
|
"Expected four octal digits as the Unix value {}!",
|
||||||
|
value.len()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
u32::from_str_radix(value, 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
let a: Config = clap::Parser::parse_from([
|
||||||
|
"cmd",
|
||||||
|
"--source=p",
|
||||||
|
"--target=p",
|
||||||
|
"--filesystem-file-permissions=0644",
|
||||||
|
]);
|
||||||
|
assert!(a.filesystem_file_permissions.is_some_and(|p| p == 0o644))
|
||||||
}
|
}
|
||||||
u32::from_str_radix(chmod, 8)
|
|
||||||
} */
|
|
||||||
|
|
|
||||||
17
src/main.rs
17
src/main.rs
|
|
@ -4,27 +4,18 @@ mod response;
|
||||||
mod snac;
|
mod snac;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use config::Config;
|
||||||
use nex::Nex;
|
use nex::Nex;
|
||||||
use response::Response;
|
use response::Response;
|
||||||
use snac::Snac;
|
use snac::Snac;
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use config::Config;
|
|
||||||
|
|
||||||
let c = Config::parse();
|
let c = Config::parse();
|
||||||
println!("Sync `{}` for users {:?}...", &c.source, &c.user);
|
let n = Nex::init(&c)?;
|
||||||
|
let s = Snac::init(&c)?;
|
||||||
|
|
||||||
let n = Nex::init(
|
println!("Sync `{}` for users {:?}...", &c.source, &c.user);
|
||||||
c.target,
|
|
||||||
c.format_filename,
|
|
||||||
c.format_updated,
|
|
||||||
c.format_content,
|
|
||||||
c.attachment,
|
|
||||||
(!c.keep, !c.daemon, c.filesystem_sync),
|
|
||||||
&c.user,
|
|
||||||
)?;
|
|
||||||
let s = Snac::init(c.source, c.user)?;
|
|
||||||
|
|
||||||
let mut r = sync(&s, &n, !c.daemon)?;
|
let mut r = sync(&s, &n, !c.daemon)?;
|
||||||
match c.rotate {
|
match c.rotate {
|
||||||
|
|
|
||||||
92
src/nex.rs
92
src/nex.rs
|
|
@ -10,7 +10,7 @@ use response::{Clean, Sync, change::Change};
|
||||||
use source::Source;
|
use source::Source;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
fs,
|
fs::{self, Permissions},
|
||||||
path::{MAIN_SEPARATOR, PathBuf},
|
path::{MAIN_SEPARATOR, PathBuf},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
@ -18,6 +18,7 @@ use template::Template;
|
||||||
|
|
||||||
pub struct Nex {
|
pub struct Nex {
|
||||||
attachment: Attachment,
|
attachment: Attachment,
|
||||||
|
file_permissions: Option<Permissions>,
|
||||||
filename: String,
|
filename: String,
|
||||||
is_cleanup: bool,
|
is_cleanup: bool,
|
||||||
is_debug: bool,
|
is_debug: bool,
|
||||||
|
|
@ -27,44 +28,50 @@ pub struct Nex {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Nex {
|
impl Nex {
|
||||||
pub fn init(
|
pub fn init(config: &crate::Config) -> Result<Self> {
|
||||||
target_dir: String,
|
|
||||||
filename: String,
|
|
||||||
time_format: String,
|
|
||||||
pattern: String,
|
|
||||||
attachment_mode: Option<String>,
|
|
||||||
(is_cleanup, is_debug, is_filesystem_sync): (bool, bool, bool),
|
|
||||||
user_names: &Vec<String>,
|
|
||||||
) -> Result<Self> {
|
|
||||||
// validate filename
|
// validate filename
|
||||||
if filename
|
if config
|
||||||
|
.format_content
|
||||||
.trim_matches(MAIN_SEPARATOR)
|
.trim_matches(MAIN_SEPARATOR)
|
||||||
.contains(MAIN_SEPARATOR)
|
.contains(MAIN_SEPARATOR)
|
||||||
{
|
{
|
||||||
bail!("File name should not contain subdir `{MAIN_SEPARATOR}` separator!")
|
bail!("File name should not contain subdir `{MAIN_SEPARATOR}` separator!")
|
||||||
}
|
}
|
||||||
// init data export location
|
// init data export location
|
||||||
let target = PathBuf::from_str(&target_dir)?.canonicalize()?;
|
let target = PathBuf::from_str(&config.target)?.canonicalize()?;
|
||||||
if !target.is_dir() {
|
if !target.is_dir() {
|
||||||
bail!("Target location is not directory!")
|
bail!("Target location is not directory!")
|
||||||
}
|
}
|
||||||
// init locations for each user
|
// init locations for each user
|
||||||
let mut users = HashMap::with_capacity(user_names.len());
|
let mut users = HashMap::with_capacity(config.user.len());
|
||||||
for u in user_names {
|
for u in &config.user {
|
||||||
let mut p = PathBuf::from(&target);
|
let mut p = PathBuf::from(&target);
|
||||||
p.push(u);
|
p.push(u);
|
||||||
fs::create_dir_all(&p)?;
|
fs::create_dir_all(&p)?;
|
||||||
users.insert(u.clone(), p);
|
users.insert(u.clone(), p);
|
||||||
}
|
}
|
||||||
// init document template formatter
|
// init document template formatter
|
||||||
let template = Template::init(&filename, pattern, time_format);
|
let template = Template::init(config);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
attachment: Attachment::init(attachment_mode)?,
|
attachment: Attachment::init(config.attachment.as_ref())?,
|
||||||
filename,
|
file_permissions: {
|
||||||
is_cleanup,
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
is_debug,
|
{
|
||||||
is_filesystem_sync,
|
use std::{fs::Permissions, os::unix::fs::PermissionsExt};
|
||||||
|
Some(Permissions::from_mode(
|
||||||
|
config.filesystem_file_permissions.unwrap_or(0o644),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
#[cfg(not(any(target_os = "linux", target_os = "macos",)))]
|
||||||
|
{
|
||||||
|
None // @TODO
|
||||||
|
}
|
||||||
|
},
|
||||||
|
filename: config.format_filename.clone(),
|
||||||
|
is_cleanup: !config.keep,
|
||||||
|
is_debug: !config.daemon,
|
||||||
|
is_filesystem_sync: config.filesystem_sync,
|
||||||
template,
|
template,
|
||||||
users,
|
users,
|
||||||
})
|
})
|
||||||
|
|
@ -136,11 +143,7 @@ impl Nex {
|
||||||
if !fs::read_to_string(&s).is_ok_and(|this| this == state) {
|
if !fs::read_to_string(&s).is_ok_and(|this| this == state) {
|
||||||
fs::remove_dir_all(&d)?;
|
fs::remove_dir_all(&d)?;
|
||||||
fs::create_dir_all(&d)?;
|
fs::create_dir_all(&d)?;
|
||||||
if self.is_filesystem_sync {
|
self.persist(&s, ×tamp, state.as_bytes())?;
|
||||||
sync(&s, timestamp.into(), state.as_bytes())?
|
|
||||||
} else {
|
|
||||||
fs::write(&s, state)?
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if let Some(ref mut i) = index {
|
if let Some(ref mut i) = index {
|
||||||
if let Some(ref a) = attachments {
|
if let Some(ref a) = attachments {
|
||||||
|
|
@ -160,12 +163,11 @@ impl Nex {
|
||||||
}
|
}
|
||||||
|
|
||||||
// save post file, set its change status
|
// save post file, set its change status
|
||||||
let change = if fs::exists(&f)? {
|
let change = self.persist(
|
||||||
Change::Updated
|
&f,
|
||||||
} else {
|
×tamp,
|
||||||
Change::Created
|
self.template
|
||||||
};
|
.build(
|
||||||
let template = self.template.build(
|
|
||||||
updated,
|
updated,
|
||||||
content,
|
content,
|
||||||
link,
|
link,
|
||||||
|
|
@ -192,6 +194,7 @@ impl Nex {
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
|
self.file_permissions.clone(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
if let Some(ref mut i) = index {
|
if let Some(ref mut i) = index {
|
||||||
|
|
@ -218,12 +221,9 @@ impl Nex {
|
||||||
}
|
}
|
||||||
b
|
b
|
||||||
}),
|
}),
|
||||||
);
|
)
|
||||||
if self.is_filesystem_sync {
|
.as_bytes(),
|
||||||
sync(&f, timestamp.into(), template.as_bytes())?
|
)?;
|
||||||
} else {
|
|
||||||
fs::write(&f, template)?
|
|
||||||
}
|
|
||||||
// move all paths processed to cleanup ignore
|
// move all paths processed to cleanup ignore
|
||||||
if let Some(ref mut i) = index {
|
if let Some(ref mut i) = index {
|
||||||
i.extend([d, f, p, s]);
|
i.extend([d, f, p, s]);
|
||||||
|
|
@ -279,13 +279,23 @@ impl Nex {
|
||||||
pub fn is_cleanup(&self) -> bool {
|
pub fn is_cleanup(&self) -> bool {
|
||||||
self.is_cleanup
|
self.is_cleanup
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper function to change time updated for new file, according to the Snac time.
|
/// Wrapper function to change time updated for new file, according to the Snac time.
|
||||||
fn sync(path: &PathBuf, modified: std::time::SystemTime, data: &[u8]) -> Result<()> {
|
fn persist(&self, path: &PathBuf, modified: &DateTime<Utc>, data: &[u8]) -> Result<Change> {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
let status = if fs::exists(path)? {
|
||||||
|
Change::Updated
|
||||||
|
} else {
|
||||||
|
Change::Created
|
||||||
|
};
|
||||||
let mut f = fs::File::create(path)?;
|
let mut f = fs::File::create(path)?;
|
||||||
f.write_all(data)?;
|
f.write_all(data)?;
|
||||||
f.set_modified(modified)?; // it's important to call after write
|
if self.is_filesystem_sync {
|
||||||
Ok(())
|
f.set_modified((*modified).into())?; // it's important to call after write
|
||||||
|
}
|
||||||
|
if let Some(ref p) = self.file_permissions {
|
||||||
|
f.set_permissions(p.clone())?
|
||||||
|
}
|
||||||
|
Ok(status)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use anyhow::{Result, bail};
|
use anyhow::{Result, bail};
|
||||||
use std::{
|
use std::{
|
||||||
|
fs::Permissions,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
time::SystemTime,
|
time::SystemTime,
|
||||||
};
|
};
|
||||||
|
|
@ -12,7 +13,7 @@ pub enum Attachment {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Attachment {
|
impl Attachment {
|
||||||
pub fn init(method: Option<String>) -> Result<Self> {
|
pub fn init(method: Option<&String>) -> Result<Self> {
|
||||||
Ok(match method {
|
Ok(match method {
|
||||||
Some(m) => match m.to_lowercase().as_str() {
|
Some(m) => match m.to_lowercase().as_str() {
|
||||||
"c" | "copy" => Self::Copy,
|
"c" | "copy" => Self::Copy,
|
||||||
|
|
@ -28,19 +29,14 @@ impl Attachment {
|
||||||
source: &PathBuf,
|
source: &PathBuf,
|
||||||
target: &PathBuf,
|
target: &PathBuf,
|
||||||
modified: Option<SystemTime>,
|
modified: Option<SystemTime>,
|
||||||
|
file_permissions: Option<Permissions>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
use std::{fs, os};
|
use std::{fs, os};
|
||||||
match self {
|
match self {
|
||||||
Attachment::Copy => {
|
Attachment::Copy => {
|
||||||
fs::copy(source, target)?;
|
fs::copy(source, target)?;
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
if let Some(p) = file_permissions {
|
||||||
{
|
fs::set_permissions(target, p)?
|
||||||
use std::{fs::Permissions, os::unix::fs::PermissionsExt};
|
|
||||||
fs::set_permissions(target, Permissions::from_mode(0o644))?; // @TODO optional
|
|
||||||
}
|
|
||||||
#[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos",)))]
|
|
||||||
{
|
|
||||||
todo!("Platform yet not supported")
|
|
||||||
}
|
}
|
||||||
println!(
|
println!(
|
||||||
"\t\t\tcopied attachment file `{}`",
|
"\t\t\tcopied attachment file `{}`",
|
||||||
|
|
@ -49,6 +45,7 @@ impl Attachment {
|
||||||
}
|
}
|
||||||
Attachment::HardLink => {
|
Attachment::HardLink => {
|
||||||
fs::hard_link(source, target)?;
|
fs::hard_link(source, target)?;
|
||||||
|
// @TODO set_permissions
|
||||||
println!(
|
println!(
|
||||||
"\t\t\tcreated hard link `{}` to attachment {}",
|
"\t\t\tcreated hard link `{}` to attachment {}",
|
||||||
target.to_string_lossy(),
|
target.to_string_lossy(),
|
||||||
|
|
@ -68,6 +65,7 @@ impl Attachment {
|
||||||
{
|
{
|
||||||
todo!("Platform yet not supported")
|
todo!("Platform yet not supported")
|
||||||
}
|
}
|
||||||
|
// @TODO set_permissions
|
||||||
println!(
|
println!(
|
||||||
"\t\t\tcreated soft link `{}` to attachment {}",
|
"\t\t\tcreated soft link `{}` to attachment {}",
|
||||||
target.to_string_lossy(),
|
target.to_string_lossy(),
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,11 @@ pub struct Template {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Template {
|
impl Template {
|
||||||
pub fn init(filename: &str, pattern: String, time_format: String) -> Self {
|
pub fn init(config: &crate::Config) -> Self {
|
||||||
Self {
|
Self {
|
||||||
format: Format::init(filename),
|
format: Format::init(&config.format_filename),
|
||||||
pattern,
|
pattern: config.format_content.clone(),
|
||||||
time_format,
|
time_format: config.format_updated.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn build(
|
pub fn build(
|
||||||
|
|
|
||||||
10
src/snac.rs
10
src/snac.rs
|
|
@ -9,15 +9,15 @@ pub struct Snac {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Snac {
|
impl Snac {
|
||||||
pub fn init(storage_dir: String, user_names: Vec<String>) -> Result<Self> {
|
pub fn init(config: &crate::Config) -> Result<Self> {
|
||||||
let storage = PathBuf::from_str(&storage_dir)?.canonicalize()?;
|
let storage = PathBuf::from_str(&config.source)?.canonicalize()?;
|
||||||
if !storage.is_dir() {
|
if !storage.is_dir() {
|
||||||
bail!("Target location is not directory!");
|
bail!("Target location is not directory!");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut users = Vec::with_capacity(user_names.len());
|
let mut users = Vec::with_capacity(config.user.len());
|
||||||
for name in user_names {
|
for name in &config.user {
|
||||||
users.push(User::init(&storage, name)?)
|
users.push(User::init(&storage, name.clone())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self { users })
|
Ok(Self { users })
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue