use std::{
fs::{copy, create_dir_all, File},
io::BufReader,
path::Path,
};
use chrono::{DateTime, Local, TimeZone};
use exif::{In, Reader, Tag};
use is_copy::is_file_copy;
use rusqlite::Connection;
use walkdir::WalkDir;
#[derive(Debug)]
struct TblInputFile {
file_path: String,
}
pub fn copy_file(suffix: &str, input_path: &Path, output_path: &Path) {
let db_path = output_path.join("file.db");
let conn = Connection::open(db_path).unwrap();
put_dir_to_db(suffix, input_path, &conn);
let sql = "SELECT file_path FROM tbl_input_file WHERE copy_time = 0";
let mut stmt = conn.prepare(sql).unwrap();
let tbl_input_file_iter = stmt
.query_map([], |row| {
Ok(TblInputFile {
file_path: row.get(0).unwrap(),
})
})
.unwrap();
'walk_dir: for tbl_input_file in tbl_input_file_iter {
let file_path = tbl_input_file.unwrap().file_path;
let input_file_path = input_path.join(&file_path);
let create_time = get_capture_date(&input_file_path);
let yyyy = create_time.format("%Y").to_string();
let yyyymm = create_time.format("%Y%m").to_string();
let output_path_date = output_path.join(yyyy).join(yyyymm);
if !output_path_date.exists() {
create_dir_all(&output_path_date).unwrap();
}
let mut output_file_path;
let mut count = 0;
'count_loop: loop {
if count == 0 {
let output_file_name = input_file_path.file_name().unwrap().to_str().unwrap();
output_file_path = output_path_date.join(output_file_name);
} else {
let file_stem = input_file_path.file_stem().unwrap().to_str().unwrap();
let extension = input_file_path.extension().unwrap().to_str().unwrap();
output_file_path =
output_path_date.join(format!("{}_{}.{}", file_stem, count, extension));
}
if output_file_path.exists() {
if is_file_copy(&input_file_path, &output_file_path) {
log::info!("file {:?} already exists", input_file_path);
continue 'walk_dir;
}
count += 1;
} else {
break 'count_loop;
}
}
log::info!("copying {:?} to {:?}", input_file_path, output_file_path);
copy(input_file_path, output_file_path).unwrap();
let sql = "UPDATE tbl_input_file SET copy_time = ?1 WHERE file_path = ?2";
conn.execute(sql, (Local::now().timestamp_millis(), file_path))
.unwrap();
}
}
fn put_dir_to_db(suffix: &str, input_path: &Path, conn: &Connection) {
let sql = "CREATE TABLE IF NOT EXISTS tbl_input_file (
file_path TEXT PRIMARY KEY,
copy_time INTEGER NOT NULL
)";
conn.execute(sql, ()).unwrap();
let sql = "SELECT * FROM tbl_input_file";
let mut stmt = conn.prepare(sql).unwrap();
if stmt.exists([]).unwrap() {
let sql = "SELECT * FROM tbl_input_file WHERE copy_time = -1";
let mut stmt = conn.prepare(sql).unwrap();
if !stmt.exists([]).unwrap() {
log::info!("cancel scan dir, all file in db.");
return;
}
}
for entry in WalkDir::new(input_path) {
let entry = entry.unwrap();
let file_type = entry.file_type();
if file_type.is_file() {
let file_name = entry.file_name();
if file_name
.to_str()
.unwrap()
.to_ascii_lowercase()
.ends_with(suffix)
{
let input_file_path = entry.path();
let file_path = input_file_path
.strip_prefix(input_path.to_str().unwrap())
.unwrap()
.to_str()
.unwrap();
log::info!("find file: {}", file_path);
let sql = format!(
"SELECT file_path FROM tbl_input_file WHERE file_path = '{}'",
file_path
);
let mut stmt = conn.prepare(&sql).unwrap();
if stmt.exists([]).unwrap() {
} else {
let sql = "INSERT INTO tbl_input_file (
file_path, copy_time
) VALUES (
?1, ?2
)";
conn.execute(sql, (file_path, -1)).unwrap();
}
}
}
}
let sql = "UPDATE tbl_input_file SET copy_time = 0 WHERE copy_time = -1";
conn.execute(sql, ()).unwrap();
}
fn get_capture_date(path: &Path) -> DateTime<Local> {
let file = File::open(path).unwrap();
let mut buf_reader = BufReader::new(file);
let reader = Reader::new();
match reader.read_from_container(&mut buf_reader) {
Ok(exif) => {
match exif.get_field(Tag::DateTimeOriginal, In::PRIMARY) {
Some(date_time) => {
let value = &date_time.display_value().to_string();
let value = value.replace("\"", "");
let mut split = "-";
if value.contains(".") {
split = ".";
}
let (year, value) = value.split_once(split).unwrap();
let (month, value) = value.split_once(split).unwrap();
let (day, value) = value.split_once(" ").unwrap();
let (hour, value) = value.split_once(":").unwrap();
let (minute, second) = value.split_once(":").unwrap();
let create_time = Local.with_ymd_and_hms(
year.parse().unwrap(),
month.parse().unwrap(),
day.parse().unwrap(),
hour.parse().unwrap(),
minute.parse().unwrap(),
second.parse().unwrap(),
);
match create_time {
chrono::LocalResult::None => {
log::error!(
"local result none, file: {:?}, date_time: {}",
path,
&date_time.display_value().to_string()
);
return Local.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
}
chrono::LocalResult::Single(create_time) => return create_time,
chrono::LocalResult::Ambiguous(min, max) => {
log::error!(
"local result ambiguous, file: {:?}, date_time: {}, min: {}, max: {}",
path,
&date_time.display_value().to_string(),min,max
);
panic!();
}
}
}
None => {
log::error!("DateTimeOriginal not exist");
return Local.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
}
}
}
Err(e) => {
log::error!("get_create_time error: {:?}", e);
return Local.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
}
}
}