-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This tool is intended to be used for debugging, allowing a person to view the data in the database in a human-readable format.
- Loading branch information
1 parent
1eb2897
commit ab4e36b
Showing
7 changed files
with
256 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/target |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
[package] | ||
name = "timetracker-dump" | ||
description = "Dumps Timetracker data to stdout or a file." | ||
version.workspace = true | ||
edition.workspace = true | ||
license.workspace = true | ||
authors.workspace = true | ||
|
||
[dependencies] | ||
anyhow = "1.0.70" | ||
chrono = "0.4.19" | ||
clap = { version = "3.2.23", features = ["std", "derive"], default-features = false } | ||
colored = { version = "2.0.0", default-features = true } | ||
config = { version = "0.13.3", features = ["toml"], default-features = false } | ||
dirs = "4.0.0" | ||
env_logger = "0.10.0" | ||
log = "0.4.17" | ||
serde = "1.0.159" | ||
serde_derive = "1.0.159" | ||
|
||
[dependencies.timetracker-core] | ||
path = "../core" | ||
|
||
[dependencies.timetracker-print-lib] | ||
path = "../print-lib" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# Dump (binary) | ||
|
||
This directory contains the Rust crate for the main Dump program. | ||
|
||
Dump will gather data from the (database) storage then write that data | ||
to stdout or to a file. | ||
|
||
## Configuration | ||
|
||
To be written. | ||
|
||
## How Dump Works | ||
|
||
To be written. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
use crate::settings::CommandArguments; | ||
use crate::settings::DumpAppSettings; | ||
use anyhow::bail; | ||
use anyhow::Result; | ||
use clap::Parser; | ||
use log::debug; | ||
use std::time::SystemTime; | ||
use timetracker_core::filesystem::get_database_file_path; | ||
use timetracker_core::settings::RECORD_INTERVAL_SECONDS; | ||
use timetracker_core::storage::Storage; | ||
use timetracker_print_lib::datetime::DateTimeLocalPair; | ||
use timetracker_print_lib::print::get_relative_week_start_end; | ||
|
||
mod settings; | ||
|
||
// CSV Spec: Each record is located on a separate line, | ||
// delimited by a line break (CRLF). | ||
static LINE_END: &str = "\r\n"; | ||
|
||
fn convert_to_csv_string_value(entry_var_name: &Option<String>) -> String { | ||
match &entry_var_name { | ||
Some(value) => value.to_string(), | ||
None => "".to_string(), | ||
} | ||
} | ||
|
||
fn generate_csv_formated_lines( | ||
storage: &mut Storage, | ||
lines: &mut Vec<String>, | ||
week_datetime_pair: DateTimeLocalPair, | ||
) -> Result<()> { | ||
let (week_start_datetime, week_end_datetime) = week_datetime_pair; | ||
|
||
let week_start_of_time = week_start_datetime.timestamp() as u64; | ||
let week_end_of_time = week_end_datetime.timestamp() as u64; | ||
let week_entries = storage.read_entries(week_start_of_time, week_end_of_time)?; | ||
|
||
for week_entry in week_entries { | ||
let line = format!( | ||
concat!( | ||
"{utc_time_seconds},{duration_seconds},", | ||
"{status:?},{executable},", | ||
"{var1_name},{var1_value},", | ||
"{var2_name},{var2_value},", | ||
"{var3_name},{var3_value},", | ||
"{var4_name},{var4_value},", | ||
"{var5_name},{var5_value}" | ||
), | ||
utc_time_seconds = week_entry.utc_time_seconds, | ||
duration_seconds = week_entry.duration_seconds, | ||
status = week_entry.status, | ||
executable = convert_to_csv_string_value(&week_entry.vars.executable), | ||
var1_name = convert_to_csv_string_value(&week_entry.vars.var1_name), | ||
var1_value = convert_to_csv_string_value(&week_entry.vars.var1_value), | ||
var2_name = convert_to_csv_string_value(&week_entry.vars.var2_name), | ||
var2_value = convert_to_csv_string_value(&week_entry.vars.var2_value), | ||
var3_name = convert_to_csv_string_value(&week_entry.vars.var3_name), | ||
var3_value = convert_to_csv_string_value(&week_entry.vars.var3_value), | ||
var4_name = convert_to_csv_string_value(&week_entry.vars.var4_name), | ||
var4_value = convert_to_csv_string_value(&week_entry.vars.var4_value), | ||
var5_name = convert_to_csv_string_value(&week_entry.vars.var5_name), | ||
var5_value = convert_to_csv_string_value(&week_entry.vars.var5_value), | ||
); | ||
lines.push(line); | ||
} | ||
Ok(()) | ||
} | ||
|
||
fn dump_database( | ||
args: &CommandArguments, | ||
settings: &DumpAppSettings, | ||
output_lines: &mut Vec<String>, | ||
) -> Result<()> { | ||
let database_file_path = get_database_file_path( | ||
&settings.core.database_dir, | ||
&settings.core.database_file_name, | ||
); | ||
|
||
let mut storage = Storage::open_as_read_only( | ||
&database_file_path.expect("Database file path should be valid"), | ||
RECORD_INTERVAL_SECONDS, | ||
)?; | ||
|
||
let relative_week = if args.last_week { | ||
-1 | ||
} else { | ||
args.relative_week | ||
}; | ||
|
||
// 'relative_week' is added to the week number to find. A value of | ||
// '-1' will get the previous week, a value of '0' will get the | ||
// current week, and a value of '1' will get the next week (which | ||
// shouldn't really give any results, so it's probably pointless). | ||
let week_datetime_pair = get_relative_week_start_end(relative_week)?; | ||
|
||
generate_csv_formated_lines(&mut storage, output_lines, week_datetime_pair) | ||
} | ||
|
||
fn main() -> Result<()> { | ||
let env = env_logger::Env::default() | ||
.filter_or("TIMETRACKER_LOG", "warn") | ||
.write_style("TIMETRACKER_LOG_STYLE"); | ||
env_logger::init_from_env(env); | ||
|
||
let args = CommandArguments::parse(); | ||
|
||
let settings = DumpAppSettings::new(&args); | ||
if settings.is_err() { | ||
bail!("Settings are invalid: {:?}", settings); | ||
} | ||
let settings = settings?; | ||
debug!("Settings validated: {:#?}", settings); | ||
|
||
let now = SystemTime::now(); | ||
|
||
let mut lines = Vec::new(); | ||
dump_database(&args, &settings, &mut lines)?; | ||
|
||
if !lines.is_empty() { | ||
// The CSV File Format header is described here: | ||
// https://www.rfc-editor.org/rfc/rfc4180#section-2 | ||
print!( | ||
"{}{}", | ||
concat!( | ||
"utc_time_seconds,duration_seconds,", | ||
"status,executable,", | ||
"var1_name,var1_value,", | ||
"var2_name,var2_value,", | ||
"var3_name,var3_value,", | ||
"var4_name,var4_value,", | ||
"var5_name,var5_value", | ||
), | ||
LINE_END | ||
); | ||
for line in &lines { | ||
print!("{}{}", line, LINE_END); | ||
} | ||
} | ||
|
||
let duration = now.elapsed()?.as_secs_f32(); | ||
debug!("Time taken: {:.2} seconds", duration); | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
use clap::Parser; | ||
use config::ConfigError; | ||
use serde_derive::Deserialize; | ||
use timetracker_core::settings::new_core_settings; | ||
use timetracker_core::settings::new_print_settings; | ||
use timetracker_core::settings::validate_core_settings; | ||
use timetracker_core::settings::CoreSettings; | ||
use timetracker_core::settings::PrintSettings; | ||
|
||
#[derive(Parser, Debug)] | ||
#[clap(author = "David Cattermole, Copyright 2023", version, about)] | ||
pub struct CommandArguments { | ||
/// Return the last week's results, shortcut for | ||
/// '--relative-week=-1'. | ||
#[clap(long, value_parser, default_value_t = false)] | ||
pub last_week: bool, | ||
|
||
/// Relative week number. '0' is the current week, '-1' is the | ||
/// previous week, etc. | ||
#[clap(short = 'w', long, value_parser, default_value_t = 0)] | ||
pub relative_week: i32, | ||
|
||
/// Override the directory to search for the database file. | ||
#[clap(long, value_parser)] | ||
pub database_dir: Option<String>, | ||
|
||
/// Override the name of the database file to open. | ||
#[clap(long, value_parser)] | ||
pub database_file_name: Option<String>, | ||
} | ||
|
||
#[derive(Debug, Deserialize)] | ||
#[allow(unused)] | ||
pub struct DumpAppSettings { | ||
pub core: CoreSettings, | ||
pub print: PrintSettings, | ||
} | ||
|
||
impl DumpAppSettings { | ||
pub fn new(arguments: &CommandArguments) -> Result<Self, ConfigError> { | ||
let builder = new_core_settings( | ||
arguments.database_dir.clone(), | ||
arguments.database_file_name.clone(), | ||
false, | ||
)?; | ||
let builder = new_print_settings(builder)?; | ||
|
||
let settings: Self = builder.build()?.try_deserialize()?; | ||
validate_core_settings(&settings.core).unwrap(); | ||
|
||
Ok(settings) | ||
} | ||
} |