Skip to content

Commit

Permalink
Split storage.rs into separate models with impl
Browse files Browse the repository at this point in the history
  • Loading branch information
rimutaka committed Aug 2, 2024
1 parent 11b7752 commit cbcaf83
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 120 deletions.
14 changes: 8 additions & 6 deletions isbn_wasm_mod/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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
Expand Down Expand Up @@ -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))))
Expand All @@ -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<storage::BookStatus>) {
pub async fn update_book_status(isbn: String, status: Option<BookStatus>) {
log!("Updating book status in local storage");

// need the runtime for the global context and fetch
Expand All @@ -119,7 +121,7 @@ pub async fn update_book_status(isbn: String, status: Option<storage::BookStatus
};

// get Books from local storage and wrap them into a response struct
let resp = match storage::Book::update_status(&runtime, &isbn, status).await {
let resp = match Book::update_status(&runtime, &isbn, status).await {
Ok(v) => {
log!("Book status updated");
WasmResponse::LocalBook(Box::new(Some(WasmResult::Ok(v))))
Expand Down Expand Up @@ -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))))
Expand Down
114 changes: 3 additions & 111 deletions isbn_wasm_mod/src/storage.rs → isbn_wasm_mod/src/models/book.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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<Book>,
}

/// 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() {
Expand Down Expand Up @@ -180,7 +163,7 @@ impl Book {
};

// store the book record in the local storage
book.store_locally(runtime);
book.save(runtime);

Ok(Some(book))
}
Expand Down Expand Up @@ -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<Self> {
// 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>(&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 })
}
}
12 changes: 12 additions & 0 deletions isbn_wasm_mod/src/models/book_status.rs
Original file line number Diff line number Diff line change
@@ -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,
}
102 changes: 102 additions & 0 deletions isbn_wasm_mod/src/models/books.rs
Original file line number Diff line number Diff line change
@@ -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<Book>,
}

impl Books {
/// Returns a sorted array of all book records stored locally.
/// Errors are logged.
pub(crate) fn get(runtime: &Window) -> Result<Self> {
// 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>(&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 })
}
}
3 changes: 3 additions & 0 deletions isbn_wasm_mod/src/models/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod book;
pub mod book_status;
pub mod books;
6 changes: 3 additions & 3 deletions isbn_wasm_mod/src/wasm_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Option<WasmResult<crate::storage::Books>>>),
LocalBooks(Box<Option<WasmResult<crate::models::books::Books>>>),
/// A single book record.
LocalBook(Box<Option<WasmResult<crate::storage::Book>>>),
LocalBook(Box<Option<WasmResult<crate::models::book::Book>>>),
/// Result of a deletion operation for the enclosed ISBN.
Deleted(Box<Option<WasmResult<String>>>)
Deleted(Box<Option<WasmResult<String>>>),
}

impl fmt::Display for WasmResponse {
Expand Down

0 comments on commit cbcaf83

Please sign in to comment.