Julio Biason
5 years ago
8 changed files with 171 additions and 73 deletions
@ -1,72 +1,159 @@ |
|||||||
use crate::config::Config; |
use std::collections::HashMap; |
||||||
use crate::config::JoplinConfig; |
|
||||||
|
|
||||||
|
use reqwest::multipart::Form; |
||||||
|
use reqwest::multipart::Part; |
||||||
use reqwest::Error; |
use reqwest::Error; |
||||||
use reqwest::Url; |
|
||||||
use serde_derive::Deserialize; |
use serde_derive::Deserialize; |
||||||
|
|
||||||
|
use crate::config::JoplinConfig; |
||||||
|
use crate::storage::data::Data; |
||||||
|
use crate::storage::storage::Storage; |
||||||
|
|
||||||
/// This is the folder structured returned by Joplin. It is here so Reqwests can
|
/// This is the folder structured returned by Joplin. It is here so Reqwests can
|
||||||
/// unjson the data (there are more fields, but these are the only ones we need
|
/// unjson the data (there are more fields, but these are the only ones we need
|
||||||
/// right now).
|
/// right now).
|
||||||
#[allow(dead_code)] |
#[allow(dead_code)] |
||||||
#[derive(Deserialize)] |
#[derive(Deserialize, Debug)] |
||||||
struct Folder { |
struct Folder { |
||||||
id: String, |
id: String, |
||||||
title: String, |
title: String, |
||||||
} |
} |
||||||
|
|
||||||
|
#[allow(dead_code)] |
||||||
|
#[derive(Deserialize, Debug)] |
||||||
|
struct Resource { |
||||||
|
id: String, |
||||||
|
filename: String, |
||||||
|
} |
||||||
|
|
||||||
/// Connection to Joplin.
|
/// Connection to Joplin.
|
||||||
pub struct JoplinConnection { |
pub struct Joplin { |
||||||
port: u32, |
port: u32, |
||||||
token: String, |
token: String, |
||||||
folder_id: String, |
folder_id: String, |
||||||
|
client: reqwest::Client, |
||||||
} |
} |
||||||
|
|
||||||
pub fn validate(config: &Config) -> Option<JoplinConnection> { |
impl Storage for Joplin { |
||||||
if let Some(joplin_config) = &config.joplin { |
fn save(&self, record: &Data) { |
||||||
let folder_id = dbg!(get_folder_id(&joplin_config)); |
let resources = dbg!(self.save_attachments(&record)); |
||||||
|
let mut text = record.text.to_string(); |
||||||
|
let title = format!("{}/{}", record.account, record.id); |
||||||
|
Joplin::add_resources_to_text(&mut text, &resources); |
||||||
|
dbg!(self.save_content(&title, &text, &record.source)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
if let Some(folder) = folder_id { |
impl Joplin { |
||||||
Some(JoplinConnection { |
pub fn new_from_config(config: &JoplinConfig) -> Joplin { |
||||||
port: joplin_config.port, |
if let Some(folder_id) = Joplin::find_folder(config) { |
||||||
token: joplin_config.token.to_string(), |
Joplin { |
||||||
folder_id: folder, |
port: config.port, |
||||||
}) |
token: config.token.to_string(), |
||||||
|
folder_id: folder_id, |
||||||
|
client: reqwest::Client::new(), |
||||||
|
} |
||||||
} else { |
} else { |
||||||
println!("No folder named {}", joplin_config.folder); |
println!("The notebook {} does not exist", &config.folder); |
||||||
None |
panic!("The specified notebook does not exist"); |
||||||
} |
} |
||||||
} else { |
|
||||||
println!("Joplin not set up"); |
|
||||||
None |
|
||||||
} |
} |
||||||
} |
|
||||||
|
|
||||||
fn build_url(config: &JoplinConfig, resource: &String) -> Url { |
|
||||||
let base_url = format!( |
|
||||||
"http://localhost:{port}/{resource}?token={token}", |
|
||||||
port = config.port, |
|
||||||
resource = resource, |
|
||||||
token = config.token |
|
||||||
); |
|
||||||
let url = Url::parse(&base_url); |
|
||||||
url.unwrap() |
|
||||||
} |
|
||||||
|
|
||||||
fn get_folder_id(config: &JoplinConfig) -> Option<String> { |
fn find_folder(config: &JoplinConfig) -> Option<String> { |
||||||
let request = get_folder_list(config); |
if let Ok(folders) = dbg!(Joplin::get_folder_list(config)) { |
||||||
if let Ok(folders) = request { |
for folder in folders { |
||||||
for folder in folders { |
if folder.title == *config.folder { |
||||||
if folder.title == *config.folder { |
return Some(folder.id); |
||||||
return Some(folder.id); |
} |
||||||
} |
} |
||||||
|
None |
||||||
|
} else { |
||||||
|
println!("Failed to retrieve the notebook list"); |
||||||
|
panic!("Failed to retrieve Joplin notebook list"); |
||||||
} |
} |
||||||
} |
} |
||||||
None |
|
||||||
} |
|
||||||
|
|
||||||
fn get_folder_list(config: &JoplinConfig) -> Result<Vec<Folder>, Error> { |
fn get_folder_list(config: &JoplinConfig) -> Result<Vec<Folder>, Error> { |
||||||
let folders: Vec<Folder> = |
let base_url = format!( |
||||||
reqwest::get(&build_url(config, &String::from("folders")).into_string())?.json()?; |
"http://localhost:{port}/folders?token={token}", |
||||||
Ok(folders) |
port = config.port, |
||||||
|
token = config.token |
||||||
|
); |
||||||
|
let folders: Vec<Folder> = reqwest::get(&base_url)?.json()?; |
||||||
|
Ok(folders) |
||||||
|
} |
||||||
|
|
||||||
|
fn add_resources_to_text(text: &mut String, resources: &Vec<Resource>) { |
||||||
|
resources.iter().for_each(|resource| { |
||||||
|
let link = format!( |
||||||
|
"![{filename}](:/{resource})", |
||||||
|
filename = resource.filename, |
||||||
|
resource = resource.id |
||||||
|
); |
||||||
|
text.push_str("\n\n"); |
||||||
|
text.push_str(&link); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
fn save_attachments(&self, record: &Data) -> Vec<Resource> { |
||||||
|
record |
||||||
|
.attachments |
||||||
|
.iter() |
||||||
|
.map(|attachment| { |
||||||
|
let mut buffer: Vec<u8> = vec![]; |
||||||
|
attachment.download().copy_to(&mut buffer).unwrap(); |
||||||
|
let resource_id = |
||||||
|
dbg!(self.upload_resource(attachment.filename().to_string(), buffer)); |
||||||
|
|
||||||
|
Resource { |
||||||
|
id: resource_id, |
||||||
|
filename: attachment.filename().to_string(), |
||||||
|
} |
||||||
|
}) |
||||||
|
.collect() |
||||||
|
} |
||||||
|
|
||||||
|
fn base_url(&self, resource: &str) -> String { |
||||||
|
format!( |
||||||
|
"http://localhost:{port}/{resource}?token={token}", |
||||||
|
port = self.port, |
||||||
|
token = self.token, |
||||||
|
resource = resource |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
fn upload_resource(&self, filename: String, content: Vec<u8>) -> String { |
||||||
|
let props = format!( |
||||||
|
"{{\"title\": \"{filename}\", \"filename\": \"{filename}\"}}", |
||||||
|
filename = &filename, |
||||||
|
); |
||||||
|
let data_part = Part::bytes(content).file_name(filename); |
||||||
|
let props_part = Part::text(props); |
||||||
|
let form = Form::new() |
||||||
|
.part("data", data_part) |
||||||
|
.part("props", props_part); |
||||||
|
let resource: Resource = self |
||||||
|
.client |
||||||
|
.post(&self.base_url("resources")) |
||||||
|
.multipart(form) |
||||||
|
.send() |
||||||
|
.unwrap() |
||||||
|
.json() |
||||||
|
.unwrap(); |
||||||
|
resource.id |
||||||
|
} |
||||||
|
|
||||||
|
fn save_content(&self, title: &String, text: &String, source: &String) { |
||||||
|
let mut request = HashMap::new(); |
||||||
|
request.insert("parent_id", &self.folder_id); |
||||||
|
request.insert("title", &title); |
||||||
|
request.insert("body", &text); |
||||||
|
request.insert("source_url", &source); |
||||||
|
|
||||||
|
self.client |
||||||
|
.post(&self.base_url("notes")) |
||||||
|
.json(&request) |
||||||
|
.send() |
||||||
|
.unwrap(); |
||||||
|
} |
||||||
} |
} |
||||||
|
Loading…
Reference in new issue