kobo-db-tools 0.0.13

A Rust library to parse and analyze Kobo e-reader SQLite databases.
Documentation
use chrono::{DateTime, Duration, Utc};
use uuid::Uuid;

#[derive(Debug, Clone, Copy)]
pub enum ReadingMetric {
    SecondsRead,
    PagesTurned,
    ButtonPressCount,
    Progress,
}

#[derive(Debug)]
pub enum ReadingSessionError {
    InvalidEndTime,
    InvalidProgressValue,
}

#[derive(Debug, Default, Clone)]
pub struct ReadingSession {
    pub id: Uuid,
    pub open_content_id: String,
    pub leave_content_id: Option<String>,
    pub time_start: DateTime<Utc>,
    pub time_end: Option<DateTime<Utc>>,
    pub volume_id: Option<String>,
    pub start_progress: u8,
    pub end_progress: Option<u8>,
    pub book_title: Option<String>,
    pub button_press_count: Option<u64>,
    pub seconds_read: Option<u64>,
    pub pages_turned: Option<u64>,
}

impl ReadingSession {
    pub fn new(
        ts: DateTime<Utc>,
        start_progress: u8,
        book_title: Option<String>,
        volume_id: Option<String>,
        open_content_id: String,
    ) -> Self {
        Self {
            id: Uuid::new_v4(),
            time_start: ts,
            time_end: None,
            open_content_id,
            leave_content_id: None,
            volume_id,
            start_progress,
            end_progress: None,
            book_title,
            button_press_count: None,
            seconds_read: None,
            pages_turned: None,
        }
    }

    pub fn complete_session(
        &mut self,
        time_end: DateTime<Utc>,
        end_progress: u8,
        button_press_count: u64,
        seconds_read: u64,
        pages_turned: u64,
        leave_content_id: String,
    ) -> Result<&mut Self, ReadingSessionError> {
        if self.time_start > time_end {
            return Err(ReadingSessionError::InvalidEndTime);
        }

        if end_progress > 100 || self.start_progress > 100 || end_progress < self.start_progress {
            return Err(ReadingSessionError::InvalidProgressValue);
        }

        self.time_end = Some(time_end);
        self.end_progress = Some(end_progress);
        self.button_press_count = Some(button_press_count);
        self.seconds_read = Some(seconds_read);
        self.pages_turned = Some(pages_turned);
        self.leave_content_id = Some(leave_content_id);
        Ok(self)
    }

    pub fn is_complete(&self) -> bool {
        self.end_progress.is_some()
    }

    pub fn duration(&self) -> Option<Duration> {
        self.time_end.map(|end| end - self.time_start)
    }
}