netscape_bookmark_parser 0.1.4

A Netspace bookmark parser for Rust
Documentation
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};

// Constants and helper functions for timestamp conversion
const LDAP_NT_EPOCH: i64 = 11644473600; // Seconds from 1601-01-01 to 1970-01-01

/// Converts LDAP/NT timestamp to Unix timestamp
/// 
/// # Arguments
/// * `ticks` - The LDAP/NT timestamp in ticks
/// 
/// # Returns
/// * The Unix timestamp in seconds
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
}

/// Recursively processes bookmark folders and writes them to the HTML file
/// 
/// # Arguments
/// * `node` - A reference to the current JSON node being processed
/// * `file` - A mutable reference to the HTML file to write to
/// 
/// # Returns
/// * `io::Result<()>` - Result indicating success or failure of file operations
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(())
}

/// Main function to convert Edge Chromium bookmarks to HTML format
/// 
/// # Arguments
/// * `input` - Path to the input JSON file containing Edge Chromium bookmarks
/// * `output` - Path to the output directory for the HTML file
/// 
/// # Returns
/// * `io::Result<()>` - Result indicating success or failure of file operations
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(())
}