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]
name = "snac2nex"
version = "0.2.2"
version = "0.3.0"
edition = "2024"
license = "MIT"
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>
Username to export
-b, --binary
Export binary files (attachments)
-a, --attachment <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 <ROTATE>
Keep running as the daemon, renew every `n` seconds

View file

@ -15,9 +15,15 @@ pub struct Config {
#[arg(short, long)]
pub user: Vec<String>,
/// 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<String>,
/// Keep running as the daemon, renew every `n` seconds
#[arg(short, long)]

View file

@ -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)
},
))
}

View file

@ -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<String>,
user_names: &Vec<String>,
) -> Result<Self> {
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)
}
}

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(())
}
}