use super::{ParsedBookmarks, ReadSource, SeekRead};
use crate::{Source, SourceBookmarks};
use anyhow::anyhow;
use log::debug;
use lz4::block;
use serde_json::{Map, Value};
#[derive(Debug)]
pub struct JsonReader;
impl ReadSource for JsonReader {
fn extension(&self) -> Option<&str> {
Some("json")
}
fn read_and_parse<'a>(
&self,
reader: &'a mut dyn SeekRead,
) -> Result<ParsedBookmarks<'a>, anyhow::Error> {
debug!("Read file with extension: {:?}", self.extension());
let mut raw_bookmarks = Vec::new();
reader.read_to_end(&mut raw_bookmarks)?;
let parsed_bookmarks = serde_json::from_slice(&raw_bookmarks)?;
Ok(ParsedBookmarks::Json(parsed_bookmarks))
}
}
#[derive(Debug)]
pub struct JsonReaderNoExtension;
impl ReadSource for JsonReaderNoExtension {
fn extension(&self) -> Option<&str> {
None
}
fn read_and_parse<'a>(
&self,
reader: &'a mut dyn SeekRead,
) -> Result<ParsedBookmarks<'a>, anyhow::Error> {
let mut raw_bookmarks = Vec::new();
reader.read_to_end(&mut raw_bookmarks)?;
let file_type = infer::get(&raw_bookmarks);
let mime_type = file_type.map(|file_type| file_type.mime_type());
debug!("Parse file with mime type {:?}", mime_type);
let parsed_bookmarks = match mime_type {
Some("text/plain") | Some("application/json") => {
serde_json::from_slice(&raw_bookmarks)?
}
Some(other) => return Err(anyhow!("File type {other} not supported")),
None => {
if let Ok(parsed_bookmarks) = serde_json::from_slice(&raw_bookmarks) {
parsed_bookmarks
} else {
return Err(anyhow!("File type not supported: {file_type:?}"));
}
}
};
Ok(ParsedBookmarks::Json(parsed_bookmarks))
}
}
#[derive(Debug)]
pub struct CompressedJsonReader;
impl ReadSource for CompressedJsonReader {
fn extension(&self) -> Option<&str> {
Some("jsonlz4")
}
fn read_and_parse<'a>(
&self,
reader: &'a mut dyn SeekRead,
) -> Result<ParsedBookmarks<'a>, anyhow::Error> {
debug!("Read file with extension: {:?}", self.extension());
let mut compressed_data = Vec::new();
reader.read_to_end(&mut compressed_data)?;
let decompressed_data = block::decompress(&compressed_data[8..], None)?;
let parsed_bookmarks = serde_json::from_slice(&decompressed_data)?;
Ok(ParsedBookmarks::Json(parsed_bookmarks))
}
}
pub fn traverse_json(
value: &Value,
source: &Source,
bookmarks: &mut SourceBookmarks,
select_bookmark: fn(&Map<String, Value>, &Source, &mut SourceBookmarks, &mut Option<String>),
select_folder: fn(&Map<String, Value>) -> Option<&String>,
) {
let mut parent_folder = None;
match value {
Value::Object(obj) => {
if source.folders.is_empty() {
select_bookmark(obj, source, bookmarks, &mut parent_folder);
for (_, val) in obj {
traverse_json(val, source, bookmarks, select_bookmark, select_folder);
}
} else {
if let Some(selected_folder) = select_folder(obj) {
parent_folder = Some(selected_folder.to_owned());
if source.folders.contains(selected_folder) {
for (_, val) in obj {
traverse_children(
val,
source,
bookmarks,
&mut parent_folder,
select_bookmark,
select_folder,
);
}
}
}
for (_, val) in obj {
traverse_json(val, source, bookmarks, select_bookmark, select_folder);
}
}
}
Value::Array(arr) => {
for val in arr {
traverse_json(val, source, bookmarks, select_bookmark, select_folder);
}
}
Value::String(_) => (),
Value::Number(_) => (),
Value::Bool(_) => (),
Value::Null => (),
}
}
fn traverse_children(
value: &Value,
source: &Source,
bookmarks: &mut SourceBookmarks,
parent_folder: &mut Option<String>,
select_bookmark: fn(&Map<String, Value>, &Source, &mut SourceBookmarks, &mut Option<String>),
select_folder: fn(&Map<String, Value>) -> Option<&String>,
) {
let mut folder = None;
match value {
Value::Object(obj) => {
select_bookmark(obj, source, bookmarks, parent_folder);
if let Some(selected_folder) = select_folder(obj) {
folder = Some(selected_folder.to_owned());
}
for (_, val) in obj {
traverse_children(
val,
source,
bookmarks,
&mut folder,
select_bookmark,
select_folder,
);
}
}
Value::Array(arr) => {
for val in arr {
traverse_children(
val,
source,
bookmarks,
parent_folder,
select_bookmark,
select_folder,
);
}
}
Value::String(_) => (),
Value::Number(_) => (),
Value::Bool(_) => (),
Value::Null => (),
}
}