remarkable_cloud_api/
lib.rs1use std::collections::HashMap;
2use std::fmt;
3use std::fs;
4use std::io;
5use std::path;
6
7use serde::de::Deserialize;
8use thiserror::Error;
9use uuid::Uuid;
10
11#[derive(Error, Debug)]
12pub enum RemarkableError {
13 #[error("result empty")]
14 EmptyResult,
15
16 #[error(transparent)]
17 IoError(#[from] io::Error),
18
19 #[error(transparent)]
20 HttpError(#[from] reqwest::Error),
21
22 #[error(transparent)]
23 JsonError(#[from] serde_json::Error),
24}
25
26#[derive(serde::Deserialize, serde::Serialize, Debug)]
27pub struct AuthTokens {
28 device_token: String,
29 user_token: String,
30}
31
32#[derive(serde::Deserialize, Debug)]
33pub struct Document {
34 #[serde(rename = "ID")]
36 pub id: Uuid,
37 #[serde(rename = "VissibleName")]
38 pub visible_name: String,
39 #[serde(rename = "Parent", deserialize_with = "deserialize_optional_uuid")]
40 pub parent: Option<Uuid>,
41 #[serde(rename = "Type")]
42 pub doc_type: String,
43 #[serde(rename = "CurrentPage")]
44 pub current_page: i32,
45 #[serde(rename = "Bookmarked")]
46 pub bookmarked: bool,
47 #[serde(rename = "Message")]
48 pub message: String,
49 #[serde(rename = "ModifiedClient")]
50 pub modified_client: chrono::DateTime<chrono::Utc>,
51 #[serde(rename = "BlobURLGet")]
52 pub blob_url_get: String,
53 #[serde(rename = "BlobURLGetExpires")]
54 pub blob_url_get_expires: chrono::DateTime<chrono::Utc>,
55}
56
57fn deserialize_optional_uuid<'de, D>(deserializer: D) -> Result<Option<Uuid>, D::Error>
59where
60 D: serde::de::Deserializer<'de>,
61{
62 let buf = String::deserialize(deserializer)?;
63
64 if buf == "" {
65 Ok(None)
66 } else {
67 Uuid::parse_str(&buf)
68 .map(Some)
69 .map_err(serde::de::Error::custom)
70 }
71}
72
73#[derive(Default)]
74pub struct Documents {
75 by_id: HashMap<Uuid, Document>,
76}
77
78impl Documents {
79 pub fn len(&self) -> usize {
80 self.by_id.len()
81 }
82
83 pub fn is_empty(&self) -> bool {
84 self.len() == 0
85 }
86
87 pub fn get(&self, uuid: &Uuid) -> Option<&Document> {
88 self.by_id.get(uuid)
89 }
90
91 pub fn get_by_path(&self, path: &path::Path) -> Option<&Document> {
92 for d in self.by_id.values() {
96 if d.visible_name
97 == path
98 .file_name()
99 .unwrap_or_default()
100 .to_str()
101 .unwrap_or_default()
102 {
103 match path.parent().zip(d.parent) {
104 None => return Some(d),
105 Some((parent_path, parent_id)) => match self.get_by_path(parent_path) {
106 None => continue,
107 Some(parent) => {
108 if parent.id == parent_id {
109 return Some(d);
110 }
111 }
112 },
113 }
114 }
115 }
116 None
117 }
118
119 pub fn get_children(&self, uuid: &Option<Uuid>) -> Vec<&Document> {
120 let mut acc: Vec<&Document> = vec![];
121 for d in self.by_id.values() {
122 if d.parent == *uuid {
123 acc.push(&d);
124 }
125 }
126 acc
127 }
128}
129
130impl<'de> serde::de::Deserialize<'de> for Documents {
131 fn deserialize<D>(deserializer: D) -> Result<Documents, D::Error>
132 where
133 D: serde::Deserializer<'de>,
134 {
135 struct DocumentsVisitor;
136
137 impl<'de> serde::de::Visitor<'de> for DocumentsVisitor {
138 type Value = Documents;
139
140 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
141 formatter.write_str("a JSON sequence")
142 }
143
144 fn visit_seq<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
145 where
146 V: serde::de::SeqAccess<'de>,
147 {
148 let mut documents: Documents = Default::default();
149
150 while let Some(doc) = visitor.next_element::<Document>()? {
151 documents.by_id.insert(doc.id, doc);
152 }
153
154 Ok(documents)
155 }
156 }
157
158 deserializer.deserialize_any(DocumentsVisitor)
159 }
160}
161
162#[derive(serde::Serialize, serde::Deserialize, Default, Debug)]
163pub struct ClientState {
164 device_token: String,
165 user_token: String,
166}
167
168impl ClientState {
169 pub fn new() -> Self {
170 Default::default()
171 }
172
173 pub fn initialize(&mut self) {
174 todo!();
175 }
176
177 pub fn load<R>(&mut self, f: R) -> Result<(), RemarkableError>
178 where
179 R: io::Read,
180 {
181 Ok(*self = serde_json::from_reader(f)?)
182 }
183
184 pub fn load_from_path(&mut self, p: &path::Path) -> Result<(), RemarkableError> {
185 Ok(self.load(io::BufReader::new(fs::File::open(p)?))?)
186 }
187
188 pub fn save<W>(&self, f: W) -> Result<(), RemarkableError>
189 where
190 W: io::Write,
191 {
192 Ok(serde_json::to_writer_pretty(f, self)?)
193 }
194
195 pub fn save_to_path(self, p: &path::Path) -> Result<(), RemarkableError> {
196 Ok(self.save(io::BufWriter::new(fs::File::create(p)?))?)
198 }
199}
200
201pub struct Client {
202 client_state: ClientState,
203 http_client: reqwest::Client,
204}
205
206impl Client {
207 pub fn new(client_state: ClientState, http_client: reqwest::Client) -> Self {
208 Client {
209 client_state,
210 http_client,
211 }
212 }
213
214 pub fn state(&mut self) -> &mut ClientState {
215 &mut self.client_state
216 }
217
218 pub async fn refresh_token(&mut self) -> Result<(), RemarkableError> {
219 let userurl = "https://my.remarkable.com/token/json/2/user/new";
220 let request = self
221 .http_client
222 .post(userurl)
223 .bearer_auth(&self.client_state.device_token)
224 .body("")
225 .header(reqwest::header::CONTENT_LENGTH, "0");
226 let response = request.send().await?;
227 self.client_state.user_token = response.text().await?;
228 Ok(())
229 }
230
231 pub async fn get_documents(&self) -> Result<Documents, RemarkableError> {
232 let endpoint = "https://document-storage-production-dot-remarkable-production.appspot.com";
233 let list = "document-storage/json/2/docs";
234 let listurl = format!("{}/{}", endpoint, list);
235 let request = self
236 .http_client
237 .get(&listurl)
238 .bearer_auth(&self.client_state.user_token);
239 let response = request.send().await?;
240 let body = response.text().await?;
241 let docs = serde_json::from_str::<Documents>(&body)?;
242 Ok(docs)
243 }
244
245 pub async fn get_document_by_id(&self, id: &Uuid) -> Result<Document, RemarkableError> {
246 let endpoint = "https://document-storage-production-dot-remarkable-production.appspot.com";
247 let list = "document-storage/json/2/docs";
248 let listurl = format!("{}/{}", endpoint, list);
249 let request = self
250 .http_client
251 .get(&listurl)
252 .bearer_auth(&self.client_state.user_token)
253 .query(&[("withBlob", "1"), ("doc", &id.to_string())]);
254 let response = request.send().await?;
255 let body = response.text().await?;
256 let mut docs = serde_json::from_str::<Documents>(&body)?;
257 match docs.by_id.remove(id) {
258 Some(d) => Ok(d),
259 None => Err(RemarkableError::EmptyResult),
260 }
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 #[test]
267 fn it_works() {
268 assert_eq!(2 + 2, 4);
269 }
270}