diff --git a/Cargo.toml b/Cargo.toml index 5499b0a..6f8e608 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "snac2nex" -version = "0.2.2" +version = "0.3.0" edition = "2024" license = "MIT" readme = "README.md" diff --git a/README.md b/README.md index 9314419..3abe1b3 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,14 @@ snac2nex -s /path/to/snac/storage -t /path/to/nex -u user1 -u user2 -u, --user Username to export - -b, --binary - Export binary files (attachments) + -a, --attachment + Include attachment files + + Supported values: + + * `c` (`copy`) - copy attachment files export + * `h` (`hard`) - create hard links + * `s` (`soft`) - create soft links (macos, linux, windows only) -r, --rotate Keep running as the daemon, renew every `n` seconds diff --git a/src/config.rs b/src/config.rs index 019b579..ad5dc07 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,9 +15,15 @@ pub struct Config { #[arg(short, long)] pub user: Vec, - /// Export binary files (attachments) - #[arg(short, long, default_value_t = false)] - pub binary: bool, + /// Include attachment files export + /// + /// Supported values: + /// + /// * `c` (`copy`) - copy attachment files + /// * `h` (`hard`) - create hard links + /// * `s` (`soft`) - create soft links (macos, linux, windows only) + #[arg(short, long)] + pub attachment: Option, /// Keep running as the daemon, renew every `n` seconds #[arg(short, long)] diff --git a/src/main.rs b/src/main.rs index ec53476..e5729ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,17 +16,18 @@ fn main() -> Result<()> { c.format_filename, c.format_updated, c.format_content, + c.attachment, &c.user, )?; let s = Snac::init(c.source, c.user)?; println!("export begin..."); - let (mut u, mut t) = sync(&s, &n, c.binary)?; + let (mut u, mut t) = sync(&s, &n)?; match c.rotate { Some(r) => loop { println!("queue completed (updated: {u} / total: {t}), await {r} seconds to rotate..."); std::thread::sleep(std::time::Duration::from_secs(r)); - (u, t) = sync(&s, &n, c.binary)?; + (u, t) = sync(&s, &n)?; }, None => println!("export completed (updated: {u} / total: {t})."), } @@ -34,7 +35,7 @@ fn main() -> Result<()> { Ok(()) } -fn sync(snac: &Snac, nex: &Nex, is_binary: bool) -> Result<(usize, usize)> { +fn sync(snac: &Snac, nex: &Nex) -> Result<(usize, usize)> { let mut t = 0; // total let mut u = 0; // updated for user in &snac.users { @@ -55,13 +56,13 @@ fn sync(snac: &Snac, nex: &Nex, is_binary: bool) -> Result<(usize, usize)> { attachments.push(( attachment.name, attachment.media_type, - if is_binary { + if nex.is_attachments_disabled() { + Source::Url(attachment.url) + } else { Source::File( user.file(attachment.url.split('/').next_back().unwrap()) .unwrap(), ) - } else { - Source::Url(attachment.url) }, )) } diff --git a/src/nex.rs b/src/nex.rs index 263f51f..210b01a 100644 --- a/src/nex.rs +++ b/src/nex.rs @@ -1,4 +1,7 @@ +mod attachment; + use anyhow::{Result, bail}; +use attachment::Attachment; use chrono::{DateTime, Utc}; use std::{collections::HashMap, fs, path::PathBuf, str::FromStr}; @@ -8,6 +11,7 @@ pub enum Source { } pub struct Nex { + attachment: Attachment, filename: String, pattern: String, time_format: String, @@ -20,6 +24,7 @@ impl Nex { filename: String, time_format: String, pattern: String, + attachment_mode: Option, user_names: &Vec, ) -> Result { use std::path::MAIN_SEPARATOR; @@ -41,9 +46,10 @@ impl Nex { } Ok(Self { + attachment: Attachment::init(attachment_mode)?, filename, - time_format, pattern, + time_format, users, }) } @@ -123,11 +129,7 @@ impl Nex { ); to.push(&f); if !to.exists() { - fs::copy(&from, &to).unwrap(); - println!( - "\t\t\tcopy attachment file `{}`", - to.to_string_lossy() - ) + self.attachment.sync(&from, &to).unwrap() } format!("{}/{f}", d.file_name().unwrap().to_string_lossy()) } @@ -171,4 +173,8 @@ impl Nex { Ok(1) } + + pub fn is_attachments_disabled(&self) -> bool { + matches!(self.attachment, Attachment::Disabled) + } } diff --git a/src/nex/attachment.rs b/src/nex/attachment.rs new file mode 100644 index 0000000..f0f8dfa --- /dev/null +++ b/src/nex/attachment.rs @@ -0,0 +1,64 @@ +use anyhow::{Result, bail}; +use std::path::PathBuf; + +pub enum Attachment { + Copy, + HardLink, + SoftLink, + Disabled, +} + +impl Attachment { + pub fn init(method: Option) -> Result { + Ok(match method { + Some(m) => match m.to_lowercase().as_str() { + "c" | "copy" => Self::Copy, + "h" | "hard" => Self::HardLink, + "s" | "soft" => Self::SoftLink, + _ => bail!("Unsupported attachment mode!"), + }, + None => Self::Disabled, + }) + } + pub fn sync(&self, source: &PathBuf, target: &PathBuf) -> Result<()> { + use std::{fs, os}; + match self { + Attachment::Copy => { + fs::copy(source, target)?; + println!( + "\t\t\tcopied attachment file `{}`", + target.to_string_lossy() + ) + } + Attachment::HardLink => { + fs::hard_link(source, target)?; + println!( + "\t\t\tcreated hard link `{}` to attachment {}", + target.to_string_lossy(), + source.to_string_lossy(), + ) + } + Attachment::SoftLink => { + #[cfg(any(target_os = "linux", target_os = "macos"))] + { + os::unix::fs::symlink(source, target)? + } + #[cfg(target_os = "windows")] + { + os::windows::fs::symlink_file(source, target)? + } + #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos",)))] + { + todo!() + } + println!( + "\t\t\tcreated soft link `{}` to attachment {}", + target.to_string_lossy(), + source.to_string_lossy(), + ) + } + _ => panic!(), // warn as unexpected + } + Ok(()) + } +}