use serde_json::{Value, from_str};
use std::fs::{self, File};
use std::io::{self, Write};
use std::path::Path;
use chrono::{DateTime, Utc, NaiveDateTime};
const LDAP_NT_EPOCH: i64 = 11644473600;
pub fn convert_to_unix_timestamp(ticks: i64) -> i64 {
let microseconds = ticks / 10;
let seconds_since_nt_epoch = microseconds / 1_000_000;
seconds_since_nt_epoch - LDAP_NT_EPOCH
}
pub fn get_bookmark_folder(node: &Value, file: &mut File) -> io::Result<()> {
if let Some(name) = node.get("name").and_then(|n| n.as_str()) {
if name == "Favorites Bar" {
let date_added = node.get("date_added").and_then(|d| d.as_str().and_then(|s| s.parse::<i64>().ok())).unwrap_or(0);
let date_modified = node.get("date_modified").and_then(|d| d.as_str().and_then(|s| s.parse::<i64>().ok())).unwrap_or(0);
writeln!(file, " <DT><H3 FOLDED ADD_DATE=\"{}\" LAST_MODIFIED=\"{}\" PERSONAL_TOOLBAR_FOLDER=\"true\">{}</H3>", convert_to_unix_timestamp(date_added), convert_to_unix_timestamp(date_modified), name)?;
writeln!(file, " <DL><p>")?;
}
if let Some(children) = node.get("children").and_then(|c| c.as_array()) {
for child in children {
let date_added = child.get("date_added").and_then(|d| d.as_str().and_then(|s| s.parse::<i64>().ok())).unwrap_or(0);
let date_modified = child.get("date_modified").and_then(|d| d.as_str().and_then(|s| s.parse::<i64>().ok())).unwrap_or(0);
if let Some(child_type) = child.get("type").and_then(|t| t.as_str()) {
if child_type == "folder" {
writeln!(file, " <DT><H3 ADD_DATE=\"{}\" LAST_MODIFIED=\"{}\">{}</H3>", convert_to_unix_timestamp(date_added), convert_to_unix_timestamp(date_modified), child.get("name").and_then(|n| n.as_str()).unwrap_or(""))?;
writeln!(file, " <DL><p>")?;
get_bookmark_folder(child, file)?;
writeln!(file, " </DL><p>")?;
} else {
if let Some(url) = child.get("url").and_then(|u| u.as_str()) {
writeln!(file, " <DT><A HREF=\"{}\" ADD_DATE=\"{}\">{}</A>", url, convert_to_unix_timestamp(date_added), child.get("name").and_then(|n| n.as_str()).unwrap_or(""))?;
}
}
}
}
}
if name == "Favorites Bar" {
writeln!(file, " </DL><p>")?;
}
}
Ok(())
}
pub fn run(input: &str, output: &str) -> io::Result<()> {
let json_file_path = Path::new(input);
let html_file_dir = Path::new(output);
let exported_time = chrono::Local::now().format("%Y-%m-%d_%H-%M-%S").to_string();
let html_file_path = html_file_dir.join(format!("EdgeChromium-Bookmarks.backup_{}.html", exported_time));
if !json_file_path.exists() {
return Err(io::Error::new(io::ErrorKind::NotFound, format!("Source file path {:?} does not exist!", json_file_path)));
}
if !html_file_dir.exists() {
return Err(io::Error::new(io::ErrorKind::NotFound, format!("Destination directory path {:?} does not exist!", html_file_dir)));
}
let json_content = fs::read_to_string(json_file_path)?;
let data: Value = from_str(&json_content)?;
let mut file = File::create(html_file_path)?;
writeln!(file, r#"<!DOCTYPE NETSCAPE-Bookmark-file-1>
<!-- This is an automatically generated file.
It will be read and overwritten.
DO NOT EDIT! -->
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
<TITLE>Bookmarks</TITLE>
<H1>Bookmarks</H1>
<DL><p>"#)?;
if let Some(roots) = data.get("roots") {
for (_, value) in roots.as_object().unwrap() {
get_bookmark_folder(value, &mut file)?;
}
}
writeln!(file, "</DL>")?;
Ok(())
}