diff --git a/isbn_wasm_mod/src/lib.rs b/isbn_wasm_mod/src/lib.rs index 4c965b8..2151529 100644 --- a/isbn_wasm_mod/src/lib.rs +++ b/isbn_wasm_mod/src/lib.rs @@ -1,4 +1,6 @@ -use storage::Book; +use models::book::Book; +use models::book_status::BookStatus; +use models::books::Books; use utils::get_runtime; use wasm_bindgen::prelude::*; use wasm_response::{report_progress, WasmResponse, WasmResult}; @@ -7,7 +9,7 @@ use wasm_response::{report_progress, WasmResponse, WasmResult}; pub(crate) mod utils; pub mod google; mod http_req; -pub mod storage; +pub mod models; pub mod wasm_response; /// All error handling in this crate is based on either retrying a request after some time @@ -82,7 +84,7 @@ pub async fn get_scanned_books() { }; // get Books from local storage and wrap them into a response struct - let resp = match storage::Books::get(&runtime) { + let resp = match Books::get(&runtime) { Ok(v) => { log!("Book list retrieved: {}", v.books.len()); WasmResponse::LocalBooks(Box::new(Some(WasmResult::Ok(v)))) @@ -104,7 +106,7 @@ pub async fn get_scanned_books() { /// Updates the status of a book in the local storage. /// Returns `WasmResponse::LocalBook::Ok` in a message if successful. #[wasm_bindgen] -pub async fn update_book_status(isbn: String, status: Option) { +pub async fn update_book_status(isbn: String, status: Option) { log!("Updating book status in local storage"); // need the runtime for the global context and fetch @@ -119,7 +121,7 @@ pub async fn update_book_status(isbn: String, status: Option { log!("Book status updated"); WasmResponse::LocalBook(Box::new(Some(WasmResult::Ok(v)))) @@ -156,7 +158,7 @@ pub async fn delete_book(isbn: String) { }; // get Books from local storage and wrap them into a response struct - let resp = match storage::Book::delete(&runtime, &isbn).await { + let resp = match Book::delete(&runtime, &isbn).await { Ok(_) => { log!("Book deleted"); WasmResponse::Deleted(Box::new(Some(WasmResult::Ok(isbn)))) diff --git a/isbn_wasm_mod/src/storage.rs b/isbn_wasm_mod/src/models/book.rs similarity index 65% rename from isbn_wasm_mod/src/storage.rs rename to isbn_wasm_mod/src/models/book.rs index c91c814..e7341ba 100644 --- a/isbn_wasm_mod/src/storage.rs +++ b/isbn_wasm_mod/src/models/book.rs @@ -1,9 +1,9 @@ +use super::book_status::BookStatus; use crate::google::{get_book_data, VolumeInfo}; use crate::utils::log; use anyhow::{bail, Result}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use wasm_bindgen::prelude::*; use web_sys::Window; /// An internal representation of a book record. @@ -28,29 +28,12 @@ pub struct Book { pub volume_info: VolumeInfo, } -/// A list of book records. -#[derive(Deserialize, Serialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct Books { - pub books: Vec, -} - -/// Where the reader is with the book. -/// Defaults to None. -#[wasm_bindgen] -#[derive(Copy, Clone, Deserialize, Serialize, Debug, PartialEq)] -pub enum BookStatus { - ToRead, - Read, - Liked, -} - impl Book { /// Adds a not to an existing book record, creates a new record if the ISBN is not found. /// The book record is stored in the local storage (front-end only access). /// Fails silently if the record cannot be stored. /// TODO: Add error handling. - pub(crate) fn store_locally(&self, runtime: &Window) { + pub(crate) fn save(&self, runtime: &Window) { // get the book record from the database let ls = match runtime.local_storage() { @@ -180,7 +163,7 @@ impl Book { }; // store the book record in the local storage - book.store_locally(runtime); + book.save(runtime); Ok(Some(book)) } @@ -211,94 +194,3 @@ impl Book { Ok(()) } } - -impl Books { - /// Returns a sorted array of all book records stored locally. - /// Errors are logged. - pub(crate) fn get(runtime: &Window) -> Result { - // connect to the local storage - let ls = match runtime.local_storage() { - Ok(Some(v)) => v, - Err(e) => { - bail!("Failed to get local storage: {:?}", e); - } - _ => { - bail!("Local storage not available (OK(None))"); - } - }; - - // get the total number of records - let number_of_records = match ls.length() { - Ok(v) => v, - Err(e) => { - bail!("Failed to get local storage length: {:?}", e); - } - }; - - // init the books array to the max possible size - let mut books = Vec::with_capacity(number_of_records.try_into().unwrap_or_else(|_e| { - log!("Failed to convert local storage length {number_of_records} to usize. It's a bug."); - 0 - })); - - // get one key at a time (inefficient, but the best we have with Local Storage) - for i in 0..number_of_records { - // get the key by index - let key = match ls.key(i) { - Ok(Some(v)) => v, - Ok(None) => { - log!("Key {i} not found in local storage"); - continue; - } - Err(e) => { - log!("Failed to get key {i} from local storage: {:?}", e); - continue; - } - }; - - // get value by key - let book = match ls.get_item(&key) { - Ok(Some(v)) => v, - Ok(None) => { - log!("Value not found in local storage: {key}"); - continue; - } - Err(e) => { - log!("Failed to get value from local storage for {key}: {:?}", e); - continue; - } - }; - - // log!("{book}"); - - // parse the string value into a book record - let book = match serde_json::from_str::(&book) { - Ok(v) => v, - Err(e) => { - log!("Failed to parse local storage book record for {key}: {:?}", e); - continue; - } - }; - - // log!("{:?}", book); - - // ignore books with no titles because it is likely to be a corrupted record - // from the format change or a bug - // the user will have no benefit from such records - // TODO: compare the ISBN inside Book and the key - they may differ - // TODO: delete these ignored records - if book.volume_info.title.is_empty() { - log!("Empty title for {key}"); - continue; - } - - books.push(book); - } - - // the items in the local storage are randomly sorted - // sort the list to make the latest scanned book come first - books.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)); - - Ok(Books { books }) - } -} diff --git a/isbn_wasm_mod/src/models/book_status.rs b/isbn_wasm_mod/src/models/book_status.rs new file mode 100644 index 0000000..3fe04cf --- /dev/null +++ b/isbn_wasm_mod/src/models/book_status.rs @@ -0,0 +1,12 @@ +use serde::{Deserialize, Serialize}; +use wasm_bindgen::prelude::*; + +/// Where the reader is with the book. +/// Defaults to None. +#[wasm_bindgen] +#[derive(Copy, Clone, Deserialize, Serialize, Debug, PartialEq)] +pub enum BookStatus { + ToRead, + Read, + Liked, +} diff --git a/isbn_wasm_mod/src/models/books.rs b/isbn_wasm_mod/src/models/books.rs new file mode 100644 index 0000000..7756485 --- /dev/null +++ b/isbn_wasm_mod/src/models/books.rs @@ -0,0 +1,102 @@ +use crate::utils::log; +use anyhow::{bail, Result}; +use serde::{Deserialize, Serialize}; +use web_sys::Window; +use super::book::Book; +/// A list of book records. +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Books { + pub books: Vec, +} + +impl Books { + /// Returns a sorted array of all book records stored locally. + /// Errors are logged. + pub(crate) fn get(runtime: &Window) -> Result { + // connect to the local storage + let ls = match runtime.local_storage() { + Ok(Some(v)) => v, + Err(e) => { + bail!("Failed to get local storage: {:?}", e); + } + _ => { + bail!("Local storage not available (OK(None))"); + } + }; + + // get the total number of records + let number_of_records = match ls.length() { + Ok(v) => v, + Err(e) => { + bail!("Failed to get local storage length: {:?}", e); + } + }; + + // init the books array to the max possible size + let mut books = Vec::with_capacity(number_of_records.try_into().unwrap_or_else(|_e| { + log!("Failed to convert local storage length {number_of_records} to usize. It's a bug."); + 0 + })); + + // get one key at a time (inefficient, but the best we have with Local Storage) + for i in 0..number_of_records { + // get the key by index + let key = match ls.key(i) { + Ok(Some(v)) => v, + Ok(None) => { + log!("Key {i} not found in local storage"); + continue; + } + Err(e) => { + log!("Failed to get key {i} from local storage: {:?}", e); + continue; + } + }; + + // get value by key + let book = match ls.get_item(&key) { + Ok(Some(v)) => v, + Ok(None) => { + log!("Value not found in local storage: {key}"); + continue; + } + Err(e) => { + log!("Failed to get value from local storage for {key}: {:?}", e); + continue; + } + }; + + // log!("{book}"); + + // parse the string value into a book record + let book = match serde_json::from_str::(&book) { + Ok(v) => v, + Err(e) => { + log!("Failed to parse local storage book record for {key}: {:?}", e); + continue; + } + }; + + // log!("{:?}", book); + + // ignore books with no titles because it is likely to be a corrupted record + // from the format change or a bug + // the user will have no benefit from such records + // TODO: compare the ISBN inside Book and the key - they may differ + // TODO: delete these ignored records + if book.volume_info.title.is_empty() { + log!("Empty title for {key}"); + continue; + } + + books.push(book); + } + + // the items in the local storage are randomly sorted + // sort the list to make the latest scanned book come first + books.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)); + + Ok(Books { books }) + } +} diff --git a/isbn_wasm_mod/src/models/mod.rs b/isbn_wasm_mod/src/models/mod.rs new file mode 100644 index 0000000..a867381 --- /dev/null +++ b/isbn_wasm_mod/src/models/mod.rs @@ -0,0 +1,3 @@ +pub mod book; +pub mod book_status; +pub mod books; \ No newline at end of file diff --git a/isbn_wasm_mod/src/wasm_response.rs b/isbn_wasm_mod/src/wasm_response.rs index e0d273c..a9b46b4 100644 --- a/isbn_wasm_mod/src/wasm_response.rs +++ b/isbn_wasm_mod/src/wasm_response.rs @@ -17,11 +17,11 @@ pub enum WasmResponse { // between the different types of responses // the memory is allocated based on the largest struct /// A list of book records. - LocalBooks(Box>>), + LocalBooks(Box>>), /// A single book record. - LocalBook(Box>>), + LocalBook(Box>>), /// Result of a deletion operation for the enclosed ISBN. - Deleted(Box>>) + Deleted(Box>>), } impl fmt::Display for WasmResponse {