add attachment symlinks option, rename binary option to attachment with export type value

This commit is contained in:
postscriptum 2025-07-02 17:10:02 +03:00
parent 9f24a133ed
commit 20c0aea8f3
6 changed files with 101 additions and 18 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "snac2nex" name = "snac2nex"
version = "0.2.2" version = "0.3.0"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
readme = "README.md" readme = "README.md"

View file

@ -30,8 +30,14 @@ snac2nex -s /path/to/snac/storage -t /path/to/nex -u user1 -u user2
-u, --user <USER> -u, --user <USER>
Username to export Username to export
-b, --binary -a, --attachment <ATTACHMENT>
Export binary files (attachments) 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 <ROTATE> -r, --rotate <ROTATE>
Keep running as the daemon, renew every `n` seconds Keep running as the daemon, renew every `n` seconds

View file

@ -15,9 +15,15 @@ pub struct Config {
#[arg(short, long)] #[arg(short, long)]
pub user: Vec<String>, pub user: Vec<String>,
/// Export binary files (attachments) /// Include attachment files export
#[arg(short, long, default_value_t = false)] ///
pub binary: bool, /// 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<String>,
/// Keep running as the daemon, renew every `n` seconds /// Keep running as the daemon, renew every `n` seconds
#[arg(short, long)] #[arg(short, long)]

View file

@ -16,17 +16,18 @@ fn main() -> Result<()> {
c.format_filename, c.format_filename,
c.format_updated, c.format_updated,
c.format_content, c.format_content,
c.attachment,
&c.user, &c.user,
)?; )?;
let s = Snac::init(c.source, c.user)?; let s = Snac::init(c.source, c.user)?;
println!("export begin..."); println!("export begin...");
let (mut u, mut t) = sync(&s, &n, c.binary)?; let (mut u, mut t) = sync(&s, &n)?;
match c.rotate { match c.rotate {
Some(r) => loop { Some(r) => loop {
println!("queue completed (updated: {u} / total: {t}), await {r} seconds to rotate..."); println!("queue completed (updated: {u} / total: {t}), await {r} seconds to rotate...");
std::thread::sleep(std::time::Duration::from_secs(r)); 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})."), None => println!("export completed (updated: {u} / total: {t})."),
} }
@ -34,7 +35,7 @@ fn main() -> Result<()> {
Ok(()) 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 t = 0; // total
let mut u = 0; // updated let mut u = 0; // updated
for user in &snac.users { for user in &snac.users {
@ -55,13 +56,13 @@ fn sync(snac: &Snac, nex: &Nex, is_binary: bool) -> Result<(usize, usize)> {
attachments.push(( attachments.push((
attachment.name, attachment.name,
attachment.media_type, attachment.media_type,
if is_binary { if nex.is_attachments_disabled() {
Source::Url(attachment.url)
} else {
Source::File( Source::File(
user.file(attachment.url.split('/').next_back().unwrap()) user.file(attachment.url.split('/').next_back().unwrap())
.unwrap(), .unwrap(),
) )
} else {
Source::Url(attachment.url)
}, },
)) ))
} }

View file

@ -1,4 +1,7 @@
mod attachment;
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use attachment::Attachment;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use std::{collections::HashMap, fs, path::PathBuf, str::FromStr}; use std::{collections::HashMap, fs, path::PathBuf, str::FromStr};
@ -8,6 +11,7 @@ pub enum Source {
} }
pub struct Nex { pub struct Nex {
attachment: Attachment,
filename: String, filename: String,
pattern: String, pattern: String,
time_format: String, time_format: String,
@ -20,6 +24,7 @@ impl Nex {
filename: String, filename: String,
time_format: String, time_format: String,
pattern: String, pattern: String,
attachment_mode: Option<String>,
user_names: &Vec<String>, user_names: &Vec<String>,
) -> Result<Self> { ) -> Result<Self> {
use std::path::MAIN_SEPARATOR; use std::path::MAIN_SEPARATOR;
@ -41,9 +46,10 @@ impl Nex {
} }
Ok(Self { Ok(Self {
attachment: Attachment::init(attachment_mode)?,
filename, filename,
time_format,
pattern, pattern,
time_format,
users, users,
}) })
} }
@ -123,11 +129,7 @@ impl Nex {
); );
to.push(&f); to.push(&f);
if !to.exists() { if !to.exists() {
fs::copy(&from, &to).unwrap(); self.attachment.sync(&from, &to).unwrap()
println!(
"\t\t\tcopy attachment file `{}`",
to.to_string_lossy()
)
} }
format!("{}/{f}", d.file_name().unwrap().to_string_lossy()) format!("{}/{f}", d.file_name().unwrap().to_string_lossy())
} }
@ -171,4 +173,8 @@ impl Nex {
Ok(1) Ok(1)
} }
pub fn is_attachments_disabled(&self) -> bool {
matches!(self.attachment, Attachment::Disabled)
}
} }

64
src/nex/attachment.rs Normal file
View file

@ -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<String>) -> Result<Self> {
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(())
}
}