mirror of
https://codeberg.org/postscriptum/snac2nex.git
synced 2026-03-31 13:15:27 +00:00
implement optional binary files copy
This commit is contained in:
parent
cf8c676732
commit
ef1ba39589
5 changed files with 101 additions and 43 deletions
49
README.md
49
README.md
|
|
@ -21,40 +21,43 @@ snac2nex -s /path/to/snac/storage -t /path/to/nex -u user1 -u user2
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
-s, --source <SOURCE>
|
-s, --source <SOURCE>
|
||||||
Path to the Snac2 profile directory
|
Path to the Snac2 profile directory
|
||||||
|
|
||||||
-t, --target <TARGET>
|
-t, --target <TARGET>
|
||||||
Target directory for public data export
|
Target directory for public data export
|
||||||
|
|
||||||
-u, --user <USER>
|
-u, --user <USER>
|
||||||
Username to export
|
Username to export
|
||||||
|
|
||||||
-r, --rotate <ROTATE>
|
-b, --binary
|
||||||
Keep running as the daemon, renew every `n` seconds
|
Export binary files (attachments)
|
||||||
|
|
||||||
-f, --format-content <FORMAT_CONTENT>
|
-r, --rotate <ROTATE>
|
||||||
Post template pattern
|
Keep running as the daemon, renew every `n` seconds
|
||||||
|
|
||||||
[default: {content}{attachments}{link}{tags}{updated}]
|
-f, --format-content <FORMAT_CONTENT>
|
||||||
|
Post template pattern
|
||||||
|
|
||||||
--format-filename <FORMAT_FILENAME>
|
[default: {content}{attachments}{link}{tags}{updated}]
|
||||||
Post filenames format
|
|
||||||
|
|
||||||
* escaped with `%%`
|
--format-filename <FORMAT_FILENAME>
|
||||||
|
Post filenames format
|
||||||
|
|
||||||
[default: %H:%M:%S.txt]
|
* escaped with `%%`
|
||||||
|
|
||||||
--format-updated <FORMAT_UPDATED>
|
[default: %H:%M:%S.txt]
|
||||||
Post `{updated}` time format
|
|
||||||
|
|
||||||
* escaped with `%%`
|
--format-updated <FORMAT_UPDATED>
|
||||||
|
Post `{updated}` time format
|
||||||
|
|
||||||
[default: "%Y/%m/%d %H:%M:%S"]
|
* escaped with `%%`
|
||||||
|
|
||||||
-h, --help
|
[default: "%Y/%m/%d %H:%M:%S"]
|
||||||
Print help (see a summary with '-h')
|
|
||||||
|
|
||||||
-V, --version
|
-h, --help
|
||||||
Print version
|
Print help (see a summary with '-h')
|
||||||
|
|
||||||
|
-V, --version
|
||||||
|
Print version
|
||||||
```
|
```
|
||||||
|
|
@ -15,6 +15,10 @@ pub struct Config {
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub user: Vec<String>,
|
pub user: Vec<String>,
|
||||||
|
|
||||||
|
/// Export binary files (attachments)
|
||||||
|
#[arg(short, long, default_value_t = false)]
|
||||||
|
pub binary: bool,
|
||||||
|
|
||||||
/// Keep running as the daemon, renew every `n` seconds
|
/// Keep running as the daemon, renew every `n` seconds
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub rotate: Option<u64>,
|
pub rotate: Option<u64>,
|
||||||
|
|
|
||||||
16
src/main.rs
16
src/main.rs
|
|
@ -21,12 +21,12 @@ fn main() -> Result<()> {
|
||||||
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)?;
|
let (mut u, mut t) = sync(&s, &n, c.binary)?;
|
||||||
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)?;
|
(u, t) = sync(&s, &n, c.binary)?;
|
||||||
},
|
},
|
||||||
None => println!("export completed (updated: {u} / total: {t})."),
|
None => println!("export completed (updated: {u} / total: {t})."),
|
||||||
}
|
}
|
||||||
|
|
@ -34,7 +34,7 @@ fn main() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sync(snac: &Snac, nex: &Nex) -> Result<(usize, usize)> {
|
fn sync(snac: &Snac, nex: &Nex, is_binary: bool) -> 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 {
|
||||||
|
|
@ -49,12 +49,20 @@ fn sync(snac: &Snac, nex: &Nex) -> Result<(usize, usize)> {
|
||||||
content,
|
content,
|
||||||
post.url,
|
post.url,
|
||||||
post.attachment.map(|a| {
|
post.attachment.map(|a| {
|
||||||
|
use nex::Source;
|
||||||
let mut attachments = Vec::with_capacity(a.len());
|
let mut attachments = Vec::with_capacity(a.len());
|
||||||
for attachment in a {
|
for attachment in a {
|
||||||
attachments.push((
|
attachments.push((
|
||||||
attachment.name,
|
attachment.name,
|
||||||
attachment.media_type,
|
attachment.media_type,
|
||||||
attachment.url,
|
if is_binary {
|
||||||
|
Source::File(
|
||||||
|
user.file(attachment.url.split('/').next_back().unwrap())
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Source::Url(attachment.url)
|
||||||
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
attachments
|
attachments
|
||||||
|
|
|
||||||
52
src/nex.rs
52
src/nex.rs
|
|
@ -1,6 +1,11 @@
|
||||||
use anyhow::{Result, bail};
|
use anyhow::{Result, bail};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use std::{collections::HashMap, path::PathBuf, str::FromStr};
|
use std::{collections::HashMap, fs, path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
|
pub enum Source {
|
||||||
|
Url(String),
|
||||||
|
File(PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Nex {
|
pub struct Nex {
|
||||||
filename: String,
|
filename: String,
|
||||||
|
|
@ -27,7 +32,7 @@ impl Nex {
|
||||||
for u in user_names {
|
for u in user_names {
|
||||||
let mut p = PathBuf::from(&target);
|
let mut p = PathBuf::from(&target);
|
||||||
p.push(u);
|
p.push(u);
|
||||||
std::fs::create_dir_all(&p)?;
|
fs::create_dir_all(&p)?;
|
||||||
users.insert(u.clone(), p);
|
users.insert(u.clone(), p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,10 +49,17 @@ impl Nex {
|
||||||
name: &str,
|
name: &str,
|
||||||
content: String,
|
content: String,
|
||||||
link: String,
|
link: String,
|
||||||
attachments: Option<Vec<(String, String, String)>>,
|
attachments: Option<Vec<(String, String, Source)>>,
|
||||||
tags: Option<Vec<String>>,
|
tags: Option<Vec<String>>,
|
||||||
(published, updated): (DateTime<Utc>, Option<DateTime<Utc>>),
|
(published, updated): (DateTime<Utc>, Option<DateTime<Utc>>),
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
// prepare destination
|
||||||
|
let mut p = PathBuf::from(self.users.get(name).unwrap());
|
||||||
|
p.push(published.format("%Y").to_string());
|
||||||
|
p.push(published.format("%m").to_string());
|
||||||
|
p.push(published.format("%d").to_string());
|
||||||
|
fs::create_dir_all(&p)?;
|
||||||
|
|
||||||
// format content pattern
|
// format content pattern
|
||||||
let c = self
|
let c = self
|
||||||
.pattern
|
.pattern
|
||||||
|
|
@ -65,9 +77,30 @@ impl Nex {
|
||||||
.map(|a| {
|
.map(|a| {
|
||||||
let mut b = Vec::with_capacity(a.len());
|
let mut b = Vec::with_capacity(a.len());
|
||||||
b.push("\n".to_string());
|
b.push("\n".to_string());
|
||||||
for (name, media_type, link) in a {
|
for (name, media_type, source) in a {
|
||||||
let mut t = Vec::with_capacity(3);
|
let mut t = Vec::with_capacity(3);
|
||||||
t.push(format!("=> {link}"));
|
t.push(format!(
|
||||||
|
"=> {}",
|
||||||
|
match source {
|
||||||
|
Source::Url(url) => url,
|
||||||
|
Source::File(from) => {
|
||||||
|
let d = format!(".{}", published.format(&self.filename));
|
||||||
|
let mut to = PathBuf::from(&p);
|
||||||
|
to.push(&d);
|
||||||
|
fs::create_dir_all(&to).unwrap();
|
||||||
|
let f = from
|
||||||
|
.file_name()
|
||||||
|
.unwrap()
|
||||||
|
.to_string_lossy()
|
||||||
|
.replace("post-", "");
|
||||||
|
to.push(&f);
|
||||||
|
if !to.exists() {
|
||||||
|
fs::copy(&from, &to).unwrap();
|
||||||
|
}
|
||||||
|
format!("{d}/{f}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
if !name.is_empty() {
|
if !name.is_empty() {
|
||||||
t.push(name)
|
t.push(name)
|
||||||
}
|
}
|
||||||
|
|
@ -94,16 +127,9 @@ impl Nex {
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// prepare destination
|
|
||||||
let mut p = PathBuf::from(self.users.get(name).unwrap());
|
|
||||||
p.push(published.format("%Y").to_string());
|
|
||||||
p.push(published.format("%m").to_string());
|
|
||||||
p.push(published.format("%d").to_string());
|
|
||||||
std::fs::create_dir_all(&p)?;
|
|
||||||
|
|
||||||
// write the data
|
// write the data
|
||||||
p.push(published.format(&self.filename).to_string());
|
p.push(published.format(&self.filename).to_string());
|
||||||
std::fs::write(p, c)?; // @TODO skip overwrite operations
|
fs::write(p, c)?; // @TODO skip overwrite operations
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,14 @@ use std::path::PathBuf;
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub public: PathBuf,
|
pub public: PathBuf,
|
||||||
|
pub root: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
pub fn init(storage: &PathBuf, name: String) -> Result<Self> {
|
pub fn init(storage: &PathBuf, name: String) -> Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
public: init(storage, &name, "public")?,
|
public: init(storage, &name, Some("public"))?,
|
||||||
|
root: init(storage, &name, None)?,
|
||||||
name,
|
name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -36,13 +38,28 @@ impl User {
|
||||||
|
|
||||||
Ok(posts)
|
Ok(posts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn file(&self, name: &str) -> Result<PathBuf> {
|
||||||
|
let mut p = PathBuf::from(&self.root);
|
||||||
|
p.push("static");
|
||||||
|
p.push(name);
|
||||||
|
|
||||||
|
if !p.exists() || !p.is_file() {
|
||||||
|
bail!("File destination `{}` not found!", p.to_string_lossy());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(p)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(storage: &PathBuf, name: &str, target: &str) -> Result<PathBuf> {
|
fn init(storage: &PathBuf, name: &str, dir: Option<&str>) -> Result<PathBuf> {
|
||||||
let mut p = PathBuf::from(&storage);
|
let mut p = PathBuf::from(&storage);
|
||||||
p.push("user");
|
p.push("user");
|
||||||
p.push(name);
|
p.push(name);
|
||||||
p.push(target);
|
|
||||||
|
if let Some(d) = dir {
|
||||||
|
p.push(d);
|
||||||
|
}
|
||||||
|
|
||||||
if !p.exists() || !p.is_dir() {
|
if !p.exists() || !p.is_dir() {
|
||||||
bail!("User data location `{}` not found!", p.to_string_lossy());
|
bail!("User data location `{}` not found!", p.to_string_lossy());
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue