Skip to content

Commit

Permalink
feat(MangaPage): Add action Bookmark selected chapter
Browse files Browse the repository at this point in the history
  • Loading branch information
josueBarretogit committed Oct 12, 2024
1 parent a4361d6 commit 4a28e4c
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 49 deletions.
87 changes: 79 additions & 8 deletions src/backend/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ use std::sync::Mutex;
use chrono::Utc;
use manga_tui::SearchTerm;
use once_cell::sync::Lazy;
use rusqlite::{params, Connection, DatabaseName};
use rusqlite::{params, Connection};
use strum::{Display, EnumIter};

use super::AppDirectories;
use crate::view::pages::manga::Bookmark;
use crate::view::widgets::feed::FeedTabs;

#[derive(Display, Debug, Clone, Copy)]
Expand Down Expand Up @@ -616,7 +615,10 @@ impl<'a> Database<'a> {
Ok(is_read)
}

fn bookmark_chapter(&mut self, chapter_id: &str, page_number: Option<u32>) -> rusqlite::Result<()> {
fn bookmark_chapter(&mut self, chapter_id: &str, manga_id: &str, page_number: Option<u32>) -> rusqlite::Result<()> {
self.connection
.execute("UPDATE chapters SET is_bookmarked = false WHERE manga_id = ?1", [manga_id])?;

self.connection
.execute("UPDATE chapters SET is_bookmarked = true, number_page_bookmarked = ?1 WHERE id = ?2", params![
page_number.unwrap_or(1),
Expand All @@ -627,9 +629,13 @@ impl<'a> Database<'a> {
}
}

pub trait Bookmark {
fn bookmark(&mut self, chapter_id: &str, manga_id: &str, page_number: Option<u32>) -> Result<(), Box<dyn std::error::Error>>;
}

impl<'a> Bookmark for Database<'a> {
fn bookmark(&mut self, chapter_id: &str, page_number: Option<u32>) -> Result<(), Box<dyn std::error::Error>> {
Ok(self.bookmark_chapter(chapter_id, page_number)?)
fn bookmark(&mut self, chapter_id: &str, manga_id: &str, page_number: Option<u32>) -> Result<(), Box<dyn std::error::Error>> {
Ok(self.bookmark_chapter(chapter_id, manga_id, page_number)?)
}
}

Expand Down Expand Up @@ -1327,7 +1333,7 @@ mod test {

#[test]
fn database_bookmarks_chapter() -> Result<()> {
let connection = Database::get_connection()?;
let connection = Connection::open_in_memory()?;
let mut database = Database::new(&connection);

database.setup()?;
Expand All @@ -1343,7 +1349,7 @@ mod test {
manga_id
])?;

database.bookmark(&chapter_id, Some(3)).expect("failed to bookmark chapter");
database.bookmark(&chapter_id, &manga_id, Some(3)).expect("failed to bookmark chapter");

let was_bookmarked: bool =
connection.query_row("SELECT is_bookmarked FROM chapters WHERE id = ?1", params![chapter_id], |row| row.get(0))?;
Expand All @@ -1355,7 +1361,7 @@ mod test {
assert!(was_bookmarked);
assert_eq!(page_set.expect("should not be null"), 3);

database.bookmark(&chapter_id, None).expect("failed to bookmark chapter");
database.bookmark(&chapter_id, &manga_id, None).expect("failed to bookmark chapter");

let page_set_to_one: Option<u32> =
connection
Expand All @@ -1364,4 +1370,69 @@ mod test {
assert_eq!(page_set_to_one.expect("should not be null"), 1);
Ok(())
}

#[test]
fn database_only_bookmarks_one_chapter_at_a_time_per_manga() -> Result<()> {
let connection = Connection::open_in_memory()?;
let mut database = Database::new(&connection);

database.setup()?;

let chapter_id = Uuid::new_v4().to_string();
let chapter_id_2 = Uuid::new_v4().to_string();
let chapter_id_should_stay_bookmarked = Uuid::new_v4().to_string();
let manga_id = Uuid::new_v4().to_string();
let manga_id_2 = Uuid::new_v4().to_string();

connection.execute("INSERT INTO mangas(id, title) VALUES(?1,?2)", params![manga_id.clone(), "some_title"])?;
connection.execute("INSERT INTO mangas(id, title) VALUES(?1,?2)", params![manga_id_2.clone(), "some_title2"])?;

connection.execute("INSERT INTO chapters(id, title, manga_id) VALUES(?1,?2,?3)", params![
chapter_id.clone(),
"some_title",
manga_id
])?;

connection.execute("INSERT INTO chapters(id, title, manga_id) VALUES(?1,?2,?3)", params![
chapter_id_2.clone(),
"some_title",
manga_id
])?;

connection.execute("INSERT INTO chapters(id, title, manga_id) VALUES(?1,?2,?3)", params![
chapter_id_should_stay_bookmarked.clone(),
"some_title",
manga_id_2
])?;

database
.bookmark_chapter(&chapter_id, &manga_id, None)
.expect("failed to bookmark chapter1");

database
.bookmark_chapter(&chapter_id_should_stay_bookmarked, &manga_id_2, None)
.expect("failed to bookmark chapter_id_should_stay_bookmarked");

database
.bookmark_chapter(&chapter_id_2, &manga_id, None)
.expect("failed to bookmark chapter2");

let was_bookmarked_1: bool =
connection.query_row("SELECT is_bookmarked FROM chapters WHERE id = ?1", params![chapter_id], |row| row.get(0))?;

let was_bookmarked_2: bool =
connection.query_row("SELECT is_bookmarked FROM chapters WHERE id = ?1", params![chapter_id_2], |row| row.get(0))?;

let should_stay_bookmarked: bool = connection.query_row(
"SELECT is_bookmarked FROM chapters WHERE id = ?1",
params![chapter_id_should_stay_bookmarked],
|row| row.get(0),
)?;

assert!(!was_bookmarked_1);
assert!(was_bookmarked_2);
assert!(should_stay_bookmarked);

Ok(())
}
}
2 changes: 1 addition & 1 deletion src/backend/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use manga_tui::SearchTerm;
use once_cell::sync::OnceCell;
use reqwest::{Client, Response, Url};

use super::api_responses::{AggregateChapterResponse, ChapterPagesResponse};
use super::api_responses::ChapterPagesResponse;
use super::filter::Languages;
use crate::backend::api_responses::OneChapterResponse;
use crate::backend::filter::{Filters, IntoParam};
Expand Down
144 changes: 112 additions & 32 deletions src/view/pages/manga.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ use crate::backend::api_responses::{
AggregateChapterResponse, ChapterPagesResponse, ChapterResponse, MangaStatisticsResponse, Statistics,
};
use crate::backend::database::{
get_chapters_history_status, save_history, set_chapter_downloaded, MangaReadingHistorySave, SetChapterDownloaded, DBCONN,
get_chapters_history_status, save_history, set_chapter_downloaded, Bookmark, Database, MangaReadingHistorySave,
SetChapterDownloaded, DBCONN,
};
use crate::backend::download::DownloadChapter;
use crate::backend::error_log::{self, write_to_error_log, ErrorType};
Expand All @@ -40,10 +41,6 @@ use crate::view::widgets::manga::{
};
use crate::view::widgets::Component;

pub trait Bookmark {
fn bookmark(&mut self, chapter_id: &str, page_number: Option<u32>) -> Result<(), Box<dyn std::error::Error>>;
}

#[derive(PartialEq, Eq, Debug)]
pub enum PageState {
DownloadingChapters,
Expand Down Expand Up @@ -73,6 +70,7 @@ pub enum MangaPageActions {
GoMangasArtist,
SearchNextChapterPage,
SearchPreviousChapterPage,
BookMarkChapterSelected,
}

#[derive(Debug, PartialEq, EnumIs)]
Expand Down Expand Up @@ -149,7 +147,7 @@ impl MangaStatistics {
}
}

#[derive(Clone, Debug)]
#[derive(Clone, Debug, Default)]
struct ChaptersData {
state: tui_widget_list::ListState,
widget: ChaptersListWidget,
Expand Down Expand Up @@ -472,6 +470,9 @@ impl MangaPage {
KeyCode::Char('b') => {
self.local_action_tx.send(MangaPageActions::SearchPreviousChapterPage).ok();
},
KeyCode::Char('m') => {
self.local_action_tx.send(MangaPageActions::BookMarkChapterSelected).ok();
},

_ => {},
}
Expand Down Expand Up @@ -522,7 +523,7 @@ impl MangaPage {
}
}

fn _get_current_selected_chapter(&self) -> Option<&ChapterItem> {
fn get_current_selected_chapter(&self) -> Option<&ChapterItem> {
match self.chapters.as_ref() {
Some(chapters_data) => match chapters_data.state.selected {
Some(selected_chapter_index) => return chapters_data.widget.chapters.get(selected_chapter_index),
Expand Down Expand Up @@ -694,11 +695,21 @@ impl MangaPage {
}
}

fn bookmark_chapter(&self, chapter_to_bookmark: &mut ChapterItem, database: &mut dyn Bookmark) {
match database.bookmark(&chapter_to_bookmark.id, None) {
Ok(()) => chapter_to_bookmark.is_bookmarked = true,
Err(_e) => {},
};
fn clear_chapters_as_bookmarked(&mut self) {
if let Some(chapters) = self.chapters.as_mut() {
chapters.widget.chapters.iter_mut().for_each(|chap| chap.is_bookmarked = false);
}
}

fn bookmark_current_chapter_selected(&mut self, database: &mut dyn Bookmark) {
self.clear_chapters_as_bookmarked();
let manga_id = self.manga.id.clone();
if let Some(chapter_selected) = self.get_current_selected_chapter_mut() {
match database.bookmark(&chapter_selected.id, &manga_id, None) {
Ok(()) => chapter_selected.is_bookmarked = true,
Err(e) => write_to_error_log(ErrorType::FromError(e)),
}
}
}

fn download_chapter_selected(&mut self) {
Expand Down Expand Up @@ -1058,6 +1069,15 @@ impl Component for MangaPage {

fn update(&mut self, action: Self::Actions) {
match action {
MangaPageActions::BookMarkChapterSelected => {
let connection = Database::get_connection();

if let Ok(conn) = connection {
let mut database = Database::new(&conn);

self.bookmark_current_chapter_selected(&mut database);
}
},
MangaPageActions::AbortDownloadAllChapters => self.abort_download_all_chapters(),
MangaPageActions::AskAbortProcces => self.ask_abort_download_chapters(),
MangaPageActions::SearchByLanguage => self.search_by_language(),
Expand Down Expand Up @@ -1108,6 +1128,10 @@ impl Component for MangaPage {
#[cfg(test)]
mod test {

use std::time::Duration;

use tokio::time::timeout;

use super::*;
use crate::backend::api_responses::ChapterData;
use crate::view::widgets::press_key;
Expand Down Expand Up @@ -1439,45 +1463,101 @@ mod test {
}

impl Bookmark for TestDatabase {
fn bookmark(&mut self, _chapter_id: &str, _page_number: Option<u32>) -> Result<(), Box<dyn std::error::Error>> {
fn bookmark(
&mut self,
_chapter_id: &str,
_manga_id: &str,
_page_number: Option<u32>,
) -> Result<(), Box<dyn std::error::Error>> {
self.chapter.was_bookmarked = true;

Ok(())
}
}

#[test]
fn it_bookmarks_given_chapter() {
let manga_page = MangaPage::new(Manga::default(), None);
#[tokio::test]
async fn it_sends_event_to_bookmark_currently_selected_chapter_on_key_press() {
let mut manga_page = MangaPage::new(Manga::default(), None);

let mut test_database = TestDatabase::new();
press_key(&mut manga_page, KeyCode::Char('m'));

let result = timeout(Duration::from_millis(250), manga_page.local_action_rx.recv())
.await
.unwrap()
.unwrap();

assert_eq!(MangaPageActions::BookMarkChapterSelected, result)
}

#[test]
fn it_bookmarks_currently_selected_chapter() {
let mut manga_page = MangaPage::new(Manga::default(), None);

let mut chapter_to_bookmark: ChapterItem = ChapterItem {
id: "chapter_id".to_string(),
is_bookmarked: false,
let chapter_to_bookmark: ChapterItem = ChapterItem {
id: "id_chapter_bookmarked".to_string(),
..Default::default()
};

manga_page.bookmark_chapter(&mut chapter_to_bookmark, &mut test_database);
let mut list_state = tui_widget_list::ListState::default();

list_state.select(Some(0));

manga_page.chapters = Some(ChaptersData {
widget: ChaptersListWidget {
chapters: vec![chapter_to_bookmark, ChapterItem::default()],
},
state: list_state,
..Default::default()
});

let mut test_database = TestDatabase::new();

manga_page.bookmark_current_chapter_selected(&mut test_database);

assert!(test_database.was_bookmarked());
assert!(chapter_to_bookmark.is_bookmarked);
let bookmarked_chapter = manga_page
.chapters
.as_ref()
.unwrap()
.widget
.chapters
.iter()
.find(|chap| chap.id == "id_chapter_bookmarked")
.unwrap();

assert!(bookmarked_chapter.is_bookmarked);
}

#[test]
fn it_bookmarks_current_selected_chapter() {
fn it_only_bookmarks_one_chapter_at_a_time() {
let mut manga_page = MangaPage::new(Manga::default(), None);

let chapter_to_bookmark: ChapterItem = ChapterItem {
id: "chapter_to_bookmark".to_string(),
is_bookmarked: false,
let mut list_state = tui_widget_list::ListState::default();

list_state.select(Some(0));

manga_page.chapters = Some(ChaptersData {
widget: ChaptersListWidget {
chapters: vec![
ChapterItem {
is_bookmarked: true,
..Default::default()
},
ChapterItem {
is_bookmarked: true,
..Default::default()
},
],
},
state: list_state,
..Default::default()
};
});

manga_page.load_chapters(Some(get_chapters_response()));
let mut test_database = TestDatabase::new();

let chapters = manga_page.chapters.as_mut().unwrap();
manga_page.bookmark_current_chapter_selected(&mut test_database);

let chapters = manga_page.get_chapter_data();

manga_page.book_mark_currently_selected_chapter();
assert!(chapters.widget.chapters[0].is_bookmarked);
assert!(!chapters.widget.chapters[1].is_bookmarked);
}
}
Loading

0 comments on commit 4a28e4c

Please sign in to comment.