1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
//! Handling of habit databases.
mod sqlite_datafile;
use anyhow::Result;
use chrono::{Duration, NaiveDate};
use std::path::Path;
/// Format of the dates used in the program.
pub const DATE_FORMAT: &str = "%Y-%m-%d";
/// Result of an update to a `DiaryDataConnection` instance.
pub enum SuccessfulUpdate {
/// The new date was not present in the instance, but it was added.
AddedNew,
/// The date was already present in the instance, but was replaced.
ReplacedExisting,
}
/// Result from the call to `add_category`
#[derive(Debug, PartialEq)]
pub enum AddCategoryResult {
/// Created a new category
AddedNew,
/// Made a previously hidden category visible again
Unhide,
/// The category is already present and is visible
AlreadyPresent,
}
/// Result from the call to `hide_category`
#[derive(Debug, PartialEq)]
pub enum HideCategoryResult {
/// The specified category was visible previously and was hidden
Hidden,
/// The specified category is already hidden, nothing was changed
AlreadyHidden,
/// The specified category does not exist
NonExistingCategory,
}
/// Represents a connection to the diary database.
pub trait DiaryDataConnection {
fn into_any(self: Box<Self>) -> Box<dyn std::any::Any>;
/// Calculates the occurences of all habits over multiple periods of date ranges.
fn calculate_data_counts_per_iter(
&self,
date_ranges: &[(NaiveDate, NaiveDate)],
) -> Result<Vec<Vec<usize>>>;
/// Modifies the provided `DiaryDataConnection` instance with the provided data row and date.
fn update_data(&mut self, date: &NaiveDate, new_row: &[usize]) -> Result<SuccessfulUpdate>;
/// Modifies the provided `DiaryDataConnection` instance with the provided row-date pairs.
fn update_data_batch(&mut self, new_items: &[(NaiveDate, Vec<usize>)]) -> Result<()>;
/// Returns a vector of missing dates between the first date in the database until specified date.
fn get_missing_dates(
&self,
from: &Option<NaiveDate>,
until: &NaiveDate,
) -> Result<Vec<NaiveDate>>;
/// Get the list of habits tracked by the database.
fn get_header(&self) -> Result<Vec<(String, usize)>>;
/// Gets the category IDs of the habits associated with the specified date,
/// or None, if the date is not present in the database.
fn get_row(&self, date: &NaiveDate) -> Result<Option<Vec<usize>>>;
/// Gets the category IDs of habits of dates that are no earlier as `from` and not more recent
/// than `until`. If the date is not in the database, None is returned for the specified date.
/// The rows are returned in date-descending order.
fn get_rows(&self, from: &NaiveDate, until: &NaiveDate) -> Result<Vec<Option<Vec<usize>>>>;
/// Returns if the database contains any records.
fn is_empty(&self) -> Result<bool>;
/// Returns the earliest and latest date among the database records.
fn get_date_range(&self) -> Result<(NaiveDate, NaiveDate)>;
/// Adds or unhides the specified category in the database.
fn add_category(&self, name: &str) -> Result<AddCategoryResult>;
/// Hides the specified category in the database.
fn hide_category(&self, name: &str) -> Result<HideCategoryResult>;
/// Returns the most frequent day "signatures" in the specified date interval (inclusive).
fn get_most_frequent_daily_data(
&self,
from: &Option<NaiveDate>,
until: &NaiveDate,
max_count: Option<usize>,
) -> Result<Vec<(Vec<usize>, usize)>>;
}
/// Tries to read data file to memory.
pub fn open_datafile(path: &Path) -> Result<Box<dyn DiaryDataConnection>> {
sqlite_datafile::open_sqlite_datafile(path)
}
/// Calculates the date ranges according to the parameters.
/// For example when `range_size == 30`, `iters == 3` and `from_date` is today,
/// the result is a 3-element vector containing ranges of the last 30 days,
/// the 30 days before that, and the 30 days before the latter one.
pub fn get_date_ranges(
from_date: &NaiveDate,
range_size: usize,
iters: usize,
) -> Vec<(NaiveDate, NaiveDate)> {
let start_offsets = (0..range_size * iters).step_by(range_size);
let end_offsets = (range_size - 1..range_size * (iters + 1)).step_by(range_size);
start_offsets
.zip(end_offsets)
.map(|(start, end)| {
(
*from_date - Duration::try_days(start as i64).unwrap(),
*from_date - Duration::try_days(end as i64).unwrap(),
)
})
.collect()
}
/// Create a new database on the prescribed path, using the prescribed headers.
pub fn create_new_datafile(path: &Path, headers: &[String]) -> Result<()> {
sqlite_datafile::create_new_sqlite(path, headers)?;
Ok(())
}
#[test]
fn test_get_date_ranges() {
let result = get_date_ranges(&NaiveDate::from_ymd_opt(2000, 5, 30).unwrap(), 5, 3);
assert_eq!(
vec![
(
NaiveDate::from_ymd_opt(2000, 5, 30).unwrap(),
NaiveDate::from_ymd_opt(2000, 5, 26).unwrap()
),
(
NaiveDate::from_ymd_opt(2000, 5, 25).unwrap(),
NaiveDate::from_ymd_opt(2000, 5, 21).unwrap()
),
(
NaiveDate::from_ymd_opt(2000, 5, 20).unwrap(),
NaiveDate::from_ymd_opt(2000, 5, 16).unwrap()
),
],
result
);
}