use std::{sync::Arc, time::Duration};
use cache::CardCache;
use card::SavedCard;
use filter::FilterUtil;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use uuid::Uuid;
pub mod cache;
pub mod card;
pub mod categories;
pub mod common;
pub mod config;
pub mod filter;
pub mod git;
pub mod media;
pub enum CardsAction {
Review,
SetSuspended(bool),
}
#[derive(Default, Clone)]
enum Field {
_Strength,
#[default]
LastModified,
}
#[derive(Default, Clone)]
struct CardSorter {
ascending: bool,
field: Field,
}
#[derive(Clone)]
enum Mode {
List { index: usize },
View { index: usize },
}
impl Mode {
fn toggle(&mut self) {
*self = match self {
Self::List { index } => Self::View { index: *index },
Self::View { index } => Self::List { index: *index },
}
}
fn is_view(&self) -> bool {
match self {
Mode::List { .. } => false,
Mode::View { .. } => true,
}
}
fn is_list(&self) -> bool {
!self.is_view()
}
fn enter_list(&mut self) {
let index = self.idx();
*self = Self::List { index };
}
fn enter_view(&mut self) {
let index = self.idx();
*self = Self::View { index };
}
fn idx(&self) -> usize {
match self {
Mode::List { index } => *index,
Mode::View { index } => *index,
}
}
fn mut_idx(&mut self) -> &mut usize {
match self {
Mode::List { index } => index,
Mode::View { index } => index,
}
}
pub fn set_idx(&mut self, idx: usize) {
*self.mut_idx() = idx;
}
}
impl Default for Mode {
fn default() -> Self {
Self::List { index: 0 }
}
}
#[derive(Clone)]
pub struct CardViewer {
cards: Vec<Id>,
filtered_cards: Vec<Id>,
filter: FilterUtil,
sorter: CardSorter,
mode: Mode,
}
impl CardViewer {
pub fn new(cards: Vec<Id>) -> Self {
if cards.is_empty() {
panic!("plz at least one element");
}
Self {
cards: cards.clone(),
filtered_cards: cards,
filter: FilterUtil::default(),
sorter: CardSorter::default(),
mode: Mode::default(),
}
}
pub fn total_qty(&self) -> usize {
self.cards.len()
}
pub fn set_idx(&mut self, idx: usize) {
self.mode.set_idx(idx);
}
pub fn filtered_qty(&self) -> usize {
self.filtered_cards.len()
}
fn update_filtered_cards(&mut self, mut cards: Vec<Id>, cache: &mut CardCache) {
match self.sorter.field {
Field::_Strength => {
cards.sort_by_key(|card| cache.get_ref(*card).strength().unwrap_or_default())
}
Field::LastModified => cards.sort_by_key(|card| cache.get_ref(*card).last_modified()),
}
if !self.sorter.ascending {
cards.reverse();
}
self.filtered_cards = cards;
}
pub fn go_forward(&mut self) {
let card_len = self.filtered_cards.len();
let idx = self.mode.mut_idx();
if *idx < card_len - 1 {
*idx += 1;
}
}
pub fn filtered_cards(&self) -> &Vec<Id> {
&self.filtered_cards
}
pub fn is_view(&self) -> bool {
self.mode.is_view()
}
pub fn is_list(&self) -> bool {
self.mode.is_list()
}
pub fn filter_ref(&self) -> &FilterUtil {
&self.filter
}
pub fn enter_view(&mut self) {
self.mode.enter_view();
}
pub fn enter_list(&mut self) {
self.mode.enter_list();
}
pub fn idx(&self) -> usize {
self.mode.idx()
}
pub fn selected_card_id(&self) -> Id {
self.filtered_cards[self.idx()]
}
pub fn selected_card(&self, cache: &mut CardCache) -> Arc<SavedCard> {
let idx = self.mode.idx();
cache.get_ref(self.filtered_cards[idx])
}
pub fn go_back(&mut self) {
let idx = self.mode.mut_idx();
if *idx > 0 {
*idx -= 1;
}
}
pub fn filter_mut(&mut self) -> &mut FilterUtil {
&mut self.filter
}
pub fn re_apply_rules(&mut self, cache: &mut CardCache) {
let cards = self.filter.evaluate_cards(self.cards.clone(), cache);
if !cards.is_empty() {
self.filtered_cards = cards;
}
}
pub fn filter(mut self, filter: FilterUtil, cache: &mut CardCache) -> Self {
let filtered_cards = filter.evaluate_cards(self.cards.clone(), cache);
if filtered_cards.is_empty() {
return self;
}
self.update_filtered_cards(filtered_cards, cache);
self.filter = filter;
*self.mode.mut_idx() = 0;
self
}
pub fn toggle_mode(&mut self) {
self.mode.toggle();
}
}
pub mod paths {
use std::path::PathBuf;
pub fn get_import_csv() -> PathBuf {
get_share_path().join("import.csv")
}
pub fn get_cards_path() -> PathBuf {
get_share_path().join("cards")
}
pub fn get_ml_path() -> PathBuf {
get_share_path().join("ml/")
}
pub fn get_runmodel_path() -> PathBuf {
get_ml_path().join("runmodel.py")
}
pub fn get_media_path() -> PathBuf {
get_share_path().join("media/")
}
#[cfg(not(test))]
pub fn get_share_path() -> PathBuf {
let home = dirs::home_dir().unwrap();
home.join(".local/share/speki/")
}
#[cfg(test)]
pub fn get_share_path() -> PathBuf {
PathBuf::from("./test_dir/")
}
}
pub type Id = Uuid;
pub fn duration_to_days(dur: &Duration) -> f32 {
dur.as_secs_f32() / 86400.
}
pub fn days_to_duration(days: f32) -> Duration {
Duration::from_secs_f32(days * 86400.)
}
pub fn serialize_duration<S>(duration: &Option<Duration>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let days = duration.as_ref().map(duration_to_days);
days.serialize(serializer)
}
pub fn deserialize_duration<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
where
D: Deserializer<'de>,
{
let days = Option::<f32>::deserialize(deserializer)?;
Ok(days.map(days_to_duration))
}