diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6936990 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..aaad0d1 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "firefox-bookmarks" +version = "0.1.0" +authors = ["Arne "] +edition = "2018" + +[dependencies] +serde_json = "1.0.32" +chrono = { version = "0.4.6", features = ["serde"] } +if_chain = "0.1.3" diff --git a/examples/bookmarks-sample.json b/examples/bookmarks-sample.json new file mode 100644 index 0000000..9ef7cec --- /dev/null +++ b/examples/bookmarks-sample.json @@ -0,0 +1,438 @@ +{ + "guid": "root________", + "title": "", + "index": 0, + "dateAdded": 1491589624976000, + "lastModified": 1539777544394000, + "id": 1, + "typeCode": 2, + "type": "text/x-moz-place-container", + "root": "placesRoot", + "children": [ + { + "guid": "menu________", + "title": "Bookmarks Menu", + "index": 0, + "dateAdded": 1491589624976000, + "lastModified": 1539777544390000, + "id": 2, + "typeCode": 2, + "type": "text/x-moz-place-container", + "root": "bookmarksMenuFolder", + "children": [ + { + "guid": "Um2yJe5O1Ncq", + "title": "Kürzlich als Lesezeichen gesetzt", + "index": 0, + "dateAdded": 1491664220957000, + "lastModified": 1491664220994000, + "id": 1146, + "typeCode": 1, + "annos": [ + { + "name": "Places/SmartBookmark", + "value": "RecentlyBookmarked", + "expires": 4, + "flags": 0 + } + ], + "type": "text/x-moz-place", + "uri": "place:parent=menu________&parent=unfiled_____&parent=toolbar_____&queryType=1&sort=12&maxResults=10&excludeQueries=1" + }, + { + "guid": "791C5kUxH6qd", + "title": "Kürzlich verwendete Schlagwörter", + "index": 1, + "dateAdded": 1491664220306000, + "lastModified": 1491664220335000, + "id": 1131, + "typeCode": 1, + "annos": [ + { + "name": "Places/SmartBookmark", + "value": "RecentTags", + "expires": 4, + "flags": 0 + } + ], + "type": "text/x-moz-place", + "uri": "place:type=6&sort=14&maxResults=10" + }, + { + "guid": "5Q9Fz5osb5TE", + "title": "", + "index": 2, + "dateAdded": 1491664220287000, + "lastModified": 1491664220287000, + "id": 1130, + "typeCode": 3, + "type": "text/x-moz-place-separator" + }, + { + "guid": "egZujOnfSOYo", + "title": "ASCII-Art", + "index": 3, + "dateAdded": 1491664168082000, + "lastModified": 1491664218958000, + "id": 73, + "typeCode": 2, + "type": "text/x-moz-place-container", + "children": [ + { + "guid": "YHYnQP8BFI_a", + "title": ":joan stark's ASCII Art Gallery:", + "index": 0, + "dateAdded": 1491664205327000, + "lastModified": 1491664205327000, + "id": 823, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/index.htm" + }, + { + "guid": "6YvIKzTZImqz", + "title": "ASCII Art by joan stark- index of new pics", + "index": 1, + "dateAdded": 1491664190619000, + "lastModified": 1491664190619000, + "id": 525, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/indexnew.htm" + }, + { + "guid": "pPsIWfHa9BCU", + "title": "ASCII Art by joan stark- index of cartoons", + "index": 2, + "dateAdded": 1491664213570000, + "lastModified": 1491664213570000, + "id": 1021, + "typeCode": 1, + "iconuri": "https://sep.yimg.com/yf/common/1.0/ysb-logo-apple-touch-icon.png", + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/indexcartoon.htm" + }, + { + "guid": "ixLhXcN9IBMO", + "title": "ASCII Art by joan stark- index of animals", + "index": 3, + "dateAdded": 1491664210698000, + "lastModified": 1491664210698000, + "id": 950, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/indexanimals.htm" + }, + { + "guid": "QsrdznMz2tQ8", + "title": "ASCII Art by joan stark- index of seasonal/holiday ASCII art", + "index": 4, + "dateAdded": 1491664201619000, + "lastModified": 1491664201619000, + "id": 735, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/indexholiday.htm" + }, + { + "guid": "iotyruwQA32m", + "title": "ASCII Art by joan stark- index of people, creatures, characters", + "index": 5, + "dateAdded": 1491664210522000, + "lastModified": 1491664210522000, + "id": 947, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/indexpeople.htm" + }, + { + "guid": "SMJSuvsNwdFW", + "title": "ASCII Art by joan stark- index of other", + "index": 6, + "dateAdded": 1491664202370000, + "lastModified": 1491664202370000, + "id": 749, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/indexother.htm" + }, + { + "guid": "JIQ9gROWlOu1", + "title": "ASCII Art by joan stark- index of colorized ASCII art", + "index": 7, + "dateAdded": 1491664197602000, + "lastModified": 1491664197602000, + "id": 639, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/indexcolor.htm" + }, + { + "guid": "_XPW_uRJCFRk", + "title": "Animiert", + "index": 8, + "dateAdded": 1491664206326000, + "lastModified": 1491664206326000, + "id": 845, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/indexjava.htm" + }, + { + "guid": "BZcdWLJZFhla", + "title": "2001", + "index": 9, + "dateAdded": 1491664166646000, + "lastModified": 1491664214295000, + "id": 45, + "typeCode": 2, + "type": "text/x-moz-place-container", + "children": [ + { + "guid": "qRVUN8DYaK0r", + "title": "April-Juni 2001", + "index": 0, + "dateAdded": 1491664214295000, + "lastModified": 1491664214295000, + "id": 1037, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/new.htm" + }, + { + "guid": "-6Rr_T1fiC-5", + "title": "Januar-März 2001", + "index": 1, + "dateAdded": 1491664186900000, + "lastModified": 1491664186900000, + "id": 438, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/01march.htm" + } + ] + }, + { + "guid": "XGCfAbJG-MzP", + "title": "2000", + "index": 10, + "dateAdded": 1491664167564000, + "lastModified": 1491664217109000, + "id": 62, + "typeCode": 2, + "type": "text/x-moz-place-container", + "children": [ + { + "guid": "cjor022uaAqi", + "title": "September-Oktober 2000", + "index": 0, + "dateAdded": 1491664207591000, + "lastModified": 1491664207591000, + "id": 873, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/00oct.htm" + }, + { + "guid": "tgjI6E6kkrIM", + "title": "November-Dezember 2000", + "index": 1, + "dateAdded": 1491664217109000, + "lastModified": 1491664217109000, + "id": 1069, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/00novdec.htm" + }, + { + "guid": "aDKJxLrPi8mR", + "title": "April-August 2000", + "index": 2, + "dateAdded": 1491664206502000, + "lastModified": 1491664206502000, + "id": 849, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/00august.htm" + }, + { + "guid": "KUvJi8STg0G_", + "title": "September 1999-März 2000", + "index": 3, + "dateAdded": 1491664198431000, + "lastModified": 1491664198431000, + "id": 658, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/00march.htm" + } + ] + }, + { + "guid": "uK22BYcA07E1", + "title": "1999", + "index": 11, + "dateAdded": 1491664170852000, + "lastModified": 1491664218958000, + "id": 103, + "typeCode": 2, + "type": "text/x-moz-place-container", + "children": [ + { + "guid": "kkSBT9RxxN71", + "title": "September 1999-März 2000", + "index": 0, + "dateAdded": 1491664211565000, + "lastModified": 1491664211565000, + "id": 971, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/00march.htm" + }, + { + "guid": "7L4HUg6an_0M", + "title": "Juli-August 1999", + "index": 1, + "dateAdded": 1491664191167000, + "lastModified": 1491664191167000, + "id": 536, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/99julyaug.htm" + }, + { + "guid": "ECS66XyyhPL5", + "title": "Juni 1999", + "index": 2, + "dateAdded": 1491664193867000, + "lastModified": 1491664193867000, + "id": 597, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/99june.htm" + }, + { + "guid": "XEZlOMdrPYCB", + "title": "Mai 1999", + "index": 3, + "dateAdded": 1491664204556000, + "lastModified": 1491664204556000, + "id": 803, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/99may.htm" + }, + { + "guid": "dypXQ3AyeRnT", + "title": "April 1999", + "index": 4, + "dateAdded": 1491664208411000, + "lastModified": 1491664208411000, + "id": 895, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/99april.htm" + }, + { + "guid": "-7R0xjRspq8U", + "title": "März 1999", + "index": 5, + "dateAdded": 1491664186950000, + "lastModified": 1491664186950000, + "id": 439, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/99march.htm" + }, + { + "guid": "xKNPTEjq8QJQ", + "title": "Februar 1999", + "index": 6, + "dateAdded": 1491664218958000, + "lastModified": 1491664218958000, + "id": 1100, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/99feb.htm" + }, + { + "guid": "ncZuWMmZf8MT", + "title": "Januar 1999", + "index": 7, + "dateAdded": 1491664212791000, + "lastModified": 1491664212791000, + "id": 1003, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/99jan.htm" + } + ] + }, + { + "guid": "7hIp0_DDUrKy", + "title": "1998", + "index": 12, + "dateAdded": 1491664165917000, + "lastModified": 1491664208760000, + "id": 38, + "typeCode": 2, + "type": "text/x-moz-place-container", + "children": [ + { + "guid": "42AlHeurRDxQ", + "title": "Dezember 1998", + "index": 0, + "dateAdded": 1491664189354000, + "lastModified": 1491664189354000, + "id": 497, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/98dec.htm" + }, + { + "guid": "61Ei_TpIaMa6", + "title": "November 1998", + "index": 1, + "dateAdded": 1491664190220000, + "lastModified": 1491664190220000, + "id": 516, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/98nov.htm" + }, + { + "guid": "edmQQYvQwYxO", + "title": "September-Oktober 1998", + "index": 2, + "dateAdded": 1491664208760000, + "lastModified": 1491664208760000, + "id": 904, + "typeCode": 1, + "type": "text/x-moz-place", + "uri": "http://www.geocities.com/SoHo/7373/98sepoct.htm" + } + ] + } + ] + }]}, + { + "guid": "mobile______", + "title": "Mobile Bookmarks", + "index": 1, + "dateAdded": 1491589624977000, + "lastModified": 1491589624977000, + "id": 6, + "typeCode": 2, + "annos": [ + { + "name": "mobile/bookmarksRoot", + "value": "1", + "expires": 4, + "flags": 0 + } + ], + "type": "text/x-moz-place-container", + "root": "mobileFolder" + } + ] +} \ No newline at end of file diff --git a/examples/read_file.rs b/examples/read_file.rs new file mode 100644 index 0000000..9d9344a --- /dev/null +++ b/examples/read_file.rs @@ -0,0 +1,7 @@ +extern crate firefox_bookmarks; +use firefox_bookmarks::parse_json; + +fn main() { + let data = parse_json(include_str!("bookmarks-sample.json")).unwrap(); + println!("{:#?}", data); +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..5569ba0 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,112 @@ +#![recursion_limit = "512"] +extern crate serde_json; +use serde_json::Value; +extern crate chrono; +use chrono::NaiveDateTime; +#[macro_use] extern crate if_chain; + +/// Either container or place +#[derive(Debug)] +pub enum Entity { + Container(PlaceContainer), + Place(Place) +} + +/// x-moz-place-container +#[derive(Debug)] +pub struct PlaceContainer { + pub guid: String, + pub title: String, + pub index: u64, + pub date_added: NaiveDateTime, + pub last_modified: NaiveDateTime, + pub id: u64, + pub root: Option, + pub children: Vec +} + +/// x-moz-place +#[derive(Debug)] +pub struct Place { + pub guid: String, + pub title: String, + pub index: u64, + pub date_added: NaiveDateTime, + pub last_modified: NaiveDateTime, + pub id: u64, + pub annos: Option, + pub uri: String +} + +/// Only `Place`s and `PlaceContainer`s will be extracted, anything else is silently discarded. +pub fn parse_json(json: &str) -> Option { + get_container(&serde_json::from_str(json).ok()?) +} + +fn get_container(json: &Value) -> Option { + if_chain! { + if Some(Some("text/x-moz-place-container")) == json.get("type").map(|x| x.as_str()); + if let Some(Some(guid)) = json.get("guid").map(|x| x.as_str().map(|x| x.to_owned())); + if let Some(Some(title)) = json.get("title").map(|x| x.as_str().map(|x| x.to_owned())); + if let Some(Some(index)) = json.get("index").map(|x| x.as_u64()); + if let Some(Some(date_added)) = json.get("dateAdded").map(|x| x.as_i64()); + if let Some(date_added) = NaiveDateTime::from_timestamp_opt(date_added / 1_000_000, (date_added % 1_000_000) as u32 * 1000); + if let Some(Some(last_modified)) = json.get("lastModified").map(|x| x.as_i64()); + if let Some(last_modified) = NaiveDateTime::from_timestamp_opt(last_modified / 1_000_000, (last_modified % 1_000_000) as u32 * 1000); + if let Some(Some(id)) = json.get("id").map(|x| x.as_u64()); + if let root = json.get("root").map(|x| x.as_str()).and_then(|x| x.map(|x| x.to_owned())); + if let Some(children) = { + if let Some(Some(children)) = json.get("children").map(|x| x.as_array()) { + let mut children_parsed = Vec::new(); + for c in children.into_iter() { + if let Some(x) = get_entity(c) { + children_parsed.push(x); + } else { + // too bad + } + } + Some(children_parsed) + } else { + Some(vec![]) + } + }; + then { + Some(PlaceContainer { + guid, title, index, date_added, last_modified, id, children, root + }) + } else { + None + } + } +} + +fn get_entity(json: &Value) -> Option { + match json.get("type").map(|x| x.as_str()) { + Some(Some("text/x-moz-place")) => Some(Entity::Place(get_place(json)?)), + Some(Some("text/x-moz-place-container")) => Some(Entity::Container(get_container(json)?)), + _ => None + } +} + +fn get_place(json: &Value) -> Option { + if_chain! { + if Some(Some("text/x-moz-place")) == json.get("type").map(|x| x.as_str()); + if let Some(Some(guid)) = json.get("guid").map(|x| x.as_str().map(|x| x.to_owned())); + if let Some(Some(title)) = json.get("title").map(|x| x.as_str().map(|x| x.to_owned())); + if let Some(Some(index)) = json.get("index").map(|x| x.as_u64()); + if let Some(Some(date_added)) = json.get("dateAdded").map(|x| x.as_i64()); + if let Some(date_added) = NaiveDateTime::from_timestamp_opt(date_added / 1_000_000, (date_added % 1_000_000) as u32 * 1000); + if let Some(Some(last_modified)) = json.get("lastModified").map(|x| x.as_i64()); + if let Some(last_modified) = NaiveDateTime::from_timestamp_opt(last_modified / 1_000_000, (last_modified % 1_000_000) as u32 * 1000); + if let Some(Some(id)) = json.get("id").map(|x| x.as_u64()); + if let annos = json.get("annos").cloned(); + if let Some(Some(uri)) = json.get("uri").map(|x| x.as_str().map(|x| x.to_owned())); + then { + Some(Place { + guid, title, index, date_added, last_modified, id, annos, uri + }) + } else { + None + } + } +} \ No newline at end of file