omega restructuring + a bunch of changes i have made over the last weeks
This commit is contained in:
544
marts/src/timetrack/mod.rs
Normal file
544
marts/src/timetrack/mod.rs
Normal file
@@ -0,0 +1,544 @@
|
||||
use std::sync::oneshot;
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use generated::spacetime::{Profile, TaskPart};
|
||||
use mize::out;
|
||||
use mize::proto::MizeMessage;
|
||||
use mize::{Mize, MizeError, MizePart, MizePartCreate, MizeResult, mize_err, mize_part};
|
||||
use spacetimedb_sdk::{DbContext, Table};
|
||||
|
||||
use generated::spacetime::{
|
||||
Action, ActivitiesTableAccess, Activity, DbConnection, TaskPartTableAccess,
|
||||
activitiesQueryTableAccess, next_action, remove_activity, task_partQueryTableAccess,
|
||||
update_activity, update_upcoming,
|
||||
};
|
||||
use crate::spacetime::SpacetimePart;
|
||||
use spacetimedb_sdk::TableWithPrimaryKey;
|
||||
|
||||
#[mize_part]
|
||||
#[derive(Default)]
|
||||
pub struct TimeTrack {
|
||||
mize: Mize,
|
||||
}
|
||||
|
||||
pub fn timetrack(mize: &mut Mize) -> MizeResult<()> {
|
||||
#[cfg(feature = "target-os")] {
|
||||
let mut cli = mize.get_part_native::<crate::CliPart>("cli")?;
|
||||
cli.set_command("ppc");
|
||||
|
||||
cli.subcommand(
|
||||
Command::new("editActionList").alias("aal").arg(
|
||||
Arg::new("defaultTemplate")
|
||||
.short('d')
|
||||
.action(ArgAction::SetTrue),
|
||||
),
|
||||
command_edit_action_list,
|
||||
)?;
|
||||
cli.subcommand(Command::new("nextAction").alias("ne").arg(
|
||||
Arg::new("break")
|
||||
.short('b')
|
||||
.action(ArgAction::SetTrue),
|
||||
).arg(
|
||||
Arg::new("action")
|
||||
.trailing_var_arg(true)
|
||||
.allow_hyphen_values(true)
|
||||
.num_args(0..), // Accepts 0 or more arguments
|
||||
), command_next_action)?;
|
||||
cli.subcommand(
|
||||
Command::new("printCurrentAction").alias("cu"),
|
||||
command_current_action,
|
||||
)?;
|
||||
cli.subcommand(
|
||||
Command::new("listActions").alias("la"),
|
||||
command_list_actions,
|
||||
)?;
|
||||
cli.subcommand(
|
||||
Command::new("listActivities").alias("lac"),
|
||||
command_list_activities,
|
||||
)?;
|
||||
cli.subcommand(
|
||||
Command::new("editActivities").alias("eac"),
|
||||
command_edit_activities,
|
||||
)?;
|
||||
mize.add_listener(
|
||||
"timetrack.sub_current",
|
||||
Box::new(timetrack_sub_current_listener),
|
||||
)?;
|
||||
|
||||
|
||||
}
|
||||
mize.register_part(Box::new(TimeTrack { mize: mize.clone() }))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_current_update_msg(conn_id: u64, data: TaskPart, mize: &mut Mize) -> MizeResult<()> {
|
||||
let now = fixed_len(data.current.name.clone(), 48);
|
||||
let next = fixed_len(
|
||||
data.upcoming
|
||||
.iter()
|
||||
.nth(0)
|
||||
.unwrap_or(&Action {
|
||||
name: "".to_string(),
|
||||
activity: None,
|
||||
task: None,
|
||||
start: None,
|
||||
end: None,
|
||||
duration: None,
|
||||
})
|
||||
.name
|
||||
.clone(),
|
||||
48,
|
||||
);
|
||||
let msg = MizeMessage::new_custom(
|
||||
"timetrack.current",
|
||||
format!(" {} NEXT: {} ", now, next),
|
||||
conn_id,
|
||||
);
|
||||
let con = mize.get_connection(conn_id)?;
|
||||
con.send(msg)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fixed_len(mut buf: String, len: usize) -> String {
|
||||
if buf.len() > len {
|
||||
buf.truncate(len - 4); // Cut off extra characters
|
||||
buf.push_str(" ...");
|
||||
} else {
|
||||
while buf.len() < len {
|
||||
buf.push(' '); // Fill with whitespace
|
||||
}
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn timetrack_sub_current_listener(msg: MizeMessage, mize: &mut Mize) -> MizeResult<()> {
|
||||
let spacetime = mize.get_part_native::<SpacetimePart>("spacetime").unwrap();
|
||||
let con = spacetime.con();
|
||||
let mut mize_clone = mize.clone();
|
||||
let mut mize_clone_two = mize.clone();
|
||||
con.subscription_builder()
|
||||
.on_applied(move |ctx| {
|
||||
let data = ctx.db.task_part().identity().find(&ctx.identity()).unwrap();
|
||||
if let Err(e) = send_current_update_msg(msg.conn_id, data, &mut mize_clone) {
|
||||
e.log();
|
||||
}
|
||||
})
|
||||
.add_query(|q| q.from.task_part())
|
||||
.subscribe();
|
||||
con.db
|
||||
.task_part()
|
||||
.on_update(move |_, _old: &TaskPart, new: &TaskPart| {
|
||||
if let Err(e) = send_current_update_msg(msg.conn_id, new.clone(), &mut mize_clone_two) {
|
||||
e.log();
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl MizePartCreate for TimeTrack {}
|
||||
|
||||
impl MizePart for TimeTrack {
|
||||
fn name(&self) -> &'static str {
|
||||
"timetrack"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
fn command_next_action(matches: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
||||
let con = spacetime.con();
|
||||
|
||||
let break_flag = matches.get_flag("break");
|
||||
let action = matches.get_many::<String>("action")
|
||||
.map(|vals| vals.map(|s| s.as_str()).collect::<Vec<&str>>().join(" "));
|
||||
let action = match action {
|
||||
Some(action) => Some(Action {
|
||||
name: action.clone(),
|
||||
activity: None,
|
||||
task: None,
|
||||
start: None,
|
||||
end: None,
|
||||
duration: None,
|
||||
}),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let old_action = get_current_action(&mut mize, con)?;
|
||||
out!("before action: {}", old_action.name);
|
||||
let (wait_tx, waiter_rx) = std::sync::oneshot::channel::<()>();
|
||||
con.reducers.next_action_then(break_flag, action, |_, _| {
|
||||
wait_tx.send(()).unwrap();
|
||||
})?;
|
||||
waiter_rx.recv()?;
|
||||
|
||||
let new_action = get_current_action(&mut mize, con)?;
|
||||
out!("new action: {}", new_action.name);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
fn command_current_action(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
||||
let action = get_current_action(&mut mize, spacetime.con())?;
|
||||
out!("{}", action.name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_current_action(_mize: &mut Mize, con: &DbConnection) -> MizeResult<Action> {
|
||||
let (wait_tx, wait_rx) = std::sync::oneshot::channel::<Action>();
|
||||
con.subscription_builder()
|
||||
.on_applied(|ctx| {
|
||||
let task_part_data = ctx.db.task_part().identity().find(&ctx.identity());
|
||||
if task_part_data.is_none() {
|
||||
wait_tx
|
||||
.send(Action {
|
||||
name: "fillier".to_string(),
|
||||
activity: None,
|
||||
task: None,
|
||||
start: None,
|
||||
end: None,
|
||||
duration: None,
|
||||
})
|
||||
.unwrap();
|
||||
} else {
|
||||
wait_tx.send(task_part_data.unwrap().current).unwrap();
|
||||
}
|
||||
})
|
||||
.add_query(|q| {
|
||||
q.from.task_part()
|
||||
//.r#where(|row| row.identity.eq(con.identity()))
|
||||
})
|
||||
.subscribe();
|
||||
let action = wait_rx.recv().unwrap();
|
||||
Ok(action)
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
fn command_list_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
||||
let activities = get_activities(&mut mize, spacetime.con())?;
|
||||
|
||||
for ac in activities.clone() {
|
||||
print!("ac {}: {}", ac.id, ac.name);
|
||||
if !ac.parents_ids.is_empty() {
|
||||
print!(" -");
|
||||
}
|
||||
for child_id in ac.parents_ids {
|
||||
let child_ac = activities.iter().find(|ac| ac.id == child_id).unwrap();
|
||||
print!(" {}", child_ac.name);
|
||||
}
|
||||
print!("\n")
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
fn command_list_actions(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
||||
|
||||
let actions = get_actions(spacetime.con())?;
|
||||
|
||||
for action in actions {
|
||||
out!("{}", action.name)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
fn command_edit_action_list(matches: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
||||
|
||||
// maybe append a template to the action list
|
||||
if matches.get_flag("defaultTemplate") {
|
||||
out!("Appending template...");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// get list of actions
|
||||
let mut actions = get_actions(spacetime.con())?;
|
||||
|
||||
// write to tmp file
|
||||
let tmp_file_content = actions_to_string(&actions)?;
|
||||
let tmp_file = std::env::temp_dir().join(format!(
|
||||
"ppc-edit-actions-{}.txt",
|
||||
chrono::Utc::now().timestamp()
|
||||
));
|
||||
std::fs::write(&tmp_file, tmp_file_content)
|
||||
.map_err(|e| mize_err!("Failed to write temp file: {}", e))?;
|
||||
|
||||
// open $EDITOR blocking
|
||||
crate::cli_spawn(format!("nvim {}", tmp_file.display()))?;
|
||||
|
||||
// read file and update db
|
||||
let new_file_content = std::fs::read_to_string(&tmp_file)
|
||||
.map_err(|e| mize_err!("Failed to read temp file: {}", e))?;
|
||||
|
||||
actions = actions_from_string(new_file_content)?;
|
||||
set_actions(actions, spacetime.con())?;
|
||||
|
||||
// delete tmpfile
|
||||
let _ = std::fs::remove_file(&tmp_file);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_actions(con: &DbConnection) -> MizeResult<Vec<Action>> {
|
||||
let (exit_tx, exit_rx) = std::sync::oneshot::channel::<Vec<Action>>();
|
||||
con.subscription_builder()
|
||||
.on_applied(|ctx| {
|
||||
let task_part_data = ctx.db.task_part().identity().find(&ctx.identity());
|
||||
if task_part_data.is_none() {
|
||||
exit_tx.send(Vec::new()).unwrap();
|
||||
} else {
|
||||
exit_tx.send(task_part_data.unwrap().upcoming).unwrap();
|
||||
}
|
||||
})
|
||||
.add_query(|q| {
|
||||
q.from.task_part()
|
||||
//.r#where(|row| row.identity.eq(con.identity()))
|
||||
})
|
||||
.subscribe();
|
||||
let activities = exit_rx.recv().unwrap();
|
||||
Ok(activities)
|
||||
}
|
||||
|
||||
fn set_actions(actions: Vec<Action>, con: &DbConnection) -> MizeResult<()> {
|
||||
let (wait_tx, waiter_rx) = std::sync::oneshot::channel::<()>();
|
||||
con.reducers
|
||||
.update_upcoming_then(actions, |_, _| wait_tx.send(()).unwrap())?;
|
||||
waiter_rx.recv().unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn actions_to_string(actions: &Vec<Action>) -> MizeResult<String> {
|
||||
let mut string = String::new();
|
||||
|
||||
for action in actions.iter() {
|
||||
string.push_str(format!("{}\n", action.name).as_str());
|
||||
}
|
||||
|
||||
Ok(string)
|
||||
}
|
||||
|
||||
fn actions_from_string(string: String) -> MizeResult<Vec<Action>> {
|
||||
let mut new_actions = Vec::new();
|
||||
for line in string.lines() {
|
||||
if line.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut action = Action {
|
||||
name: line.to_owned(),
|
||||
activity: None,
|
||||
task: None,
|
||||
start: None,
|
||||
end: None,
|
||||
duration: None,
|
||||
};
|
||||
parse_action(&mut action);
|
||||
new_actions.push(action);
|
||||
}
|
||||
Ok(new_actions)
|
||||
}
|
||||
|
||||
fn parse_action(action: &mut Action) {
|
||||
let _parts: Vec<&str> = action.name.split_whitespace().collect();
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
fn command_edit_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
||||
|
||||
// get list of activities
|
||||
let activities = get_activities(&mut mize, spacetime.con())?;
|
||||
|
||||
let con = spacetime.con();
|
||||
|
||||
// write to tmp file
|
||||
let mut tmp_file_content = String::new();
|
||||
for ac in activities.iter() {
|
||||
tmp_file_content.push_str(format!("{} {}", ac.id, ac.name).as_str());
|
||||
if !ac.parents_ids.is_empty() {
|
||||
tmp_file_content.push_str(" -");
|
||||
for parent_id in ac.parents_ids.iter() {
|
||||
let parent_ac = activities.iter().find(|ac| ac.id == *parent_id).unwrap();
|
||||
tmp_file_content.push_str(" ");
|
||||
tmp_file_content.push_str(&parent_ac.name);
|
||||
}
|
||||
}
|
||||
tmp_file_content.push_str("\n");
|
||||
}
|
||||
let tmp_file = std::env::temp_dir().join(format!(
|
||||
"ppc-edit-activities-{}.txt",
|
||||
chrono::Utc::now().timestamp()
|
||||
));
|
||||
std::fs::write(&tmp_file, tmp_file_content)
|
||||
.map_err(|e| mize_err!("Failed to write temp file: {}", e))?;
|
||||
|
||||
loop {
|
||||
// open $EDITOR blocking
|
||||
crate::cli_spawn(format!("nvim {}", tmp_file.display()))?;
|
||||
|
||||
let new_file_content = std::fs::read_to_string(&tmp_file)
|
||||
.map_err(|e| mize_err!("Failed to read temp file: {}", e))?;
|
||||
|
||||
let mut input = String::new();
|
||||
|
||||
let old_activities = get_activities(&mut mize, con)?;
|
||||
if let Err(err) =
|
||||
parse_activities_from_text(&mut mize, con, new_file_content, old_activities)
|
||||
{
|
||||
out!("error parsing the activities from the file: {:?}", err);
|
||||
out!("press ENTER to open the file again");
|
||||
|
||||
// wait for user input
|
||||
// so that they can se the err msg, before running nvim again
|
||||
std::io::stdin().read_line(&mut input)?;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// delete tmpfile
|
||||
let _ = std::fs::remove_file(&tmp_file);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_activities(_mize: &mut Mize, con: &DbConnection) -> MizeResult<Vec<Activity>> {
|
||||
let (exit_tx, exit_rx) = std::sync::oneshot::channel::<Vec<Activity>>();
|
||||
con.subscription_builder()
|
||||
.on_applied(|ctx| {
|
||||
exit_tx.send(ctx.db.activities().iter().collect()).unwrap();
|
||||
})
|
||||
.add_query(|q| q.from.activities())
|
||||
.subscribe();
|
||||
let activities = exit_rx.recv().unwrap();
|
||||
Ok(activities)
|
||||
}
|
||||
|
||||
fn parse_activities_from_text(
|
||||
mize: &mut Mize,
|
||||
con: &DbConnection,
|
||||
text: String,
|
||||
old_activities: Vec<Activity>,
|
||||
) -> MizeResult<()> {
|
||||
let mut activities_to_delete = old_activities.clone();
|
||||
let mut wait_revievers = Vec::new();
|
||||
for line in text.lines() {
|
||||
if line == "" {
|
||||
continue;
|
||||
}
|
||||
|
||||
// split
|
||||
let mut parts = line.split(" ");
|
||||
let id: u64 = parts.next().ok_or(mize_err!(".."))?.parse()?;
|
||||
let name = parts.next().ok_or(mize_err!(".."))?.to_string();
|
||||
|
||||
// remove from to-delete list
|
||||
if let Some(pos) = activities_to_delete.iter().position(|ac| ac.id == id) {
|
||||
activities_to_delete.remove(pos);
|
||||
}
|
||||
|
||||
// wait rx
|
||||
let (wait_tx, wait_rx) = oneshot::channel::<()>();
|
||||
wait_revievers.push(wait_rx);
|
||||
|
||||
// update or create activity
|
||||
if let Some(old_activity) = old_activities.iter().find(|ac| ac.id == id) {
|
||||
con.reducers.update_activity_then(
|
||||
Activity {
|
||||
id,
|
||||
name,
|
||||
task: old_activity.task,
|
||||
parents_ids: old_activity.parents_ids.clone(),
|
||||
},
|
||||
move |_, _| {
|
||||
let _ = wait_tx.send(());
|
||||
},
|
||||
)?;
|
||||
} else {
|
||||
con.reducers.update_activity_then(
|
||||
Activity {
|
||||
id: 0,
|
||||
name,
|
||||
task: None,
|
||||
parents_ids: Vec::new(),
|
||||
},
|
||||
move |_, _| {
|
||||
let _ = wait_tx.send(());
|
||||
},
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
// remove deleted activities
|
||||
for activity in activities_to_delete {
|
||||
let (wait_tx, wait_rx) = oneshot::channel::<()>();
|
||||
wait_revievers.push(wait_rx);
|
||||
con.reducers.remove_activity_then(activity, move |_, _| {
|
||||
let _ = wait_tx.send(());
|
||||
})?;
|
||||
}
|
||||
|
||||
// wait for db stuff to finish
|
||||
for wait_rx in wait_revievers {
|
||||
wait_rx.recv().unwrap();
|
||||
}
|
||||
let mut wait_revievers = Vec::new();
|
||||
|
||||
// check for declared parents
|
||||
let new_activities = get_activities(mize, con)?;
|
||||
out!("new_activities: {:?}", new_activities);
|
||||
for line in text.lines() {
|
||||
if line == "" {
|
||||
continue;
|
||||
}
|
||||
|
||||
// split
|
||||
let mut parts = line.split(" ");
|
||||
let _id: u64 = parts.next().ok_or(mize_err!(".."))?.parse()?;
|
||||
let name = parts.next().ok_or(mize_err!(".."))?.to_string();
|
||||
|
||||
// parse parents
|
||||
let seperator = parts.next();
|
||||
if seperator.is_some() && seperator.unwrap() == "-" {
|
||||
let mut parents_ids = Vec::new();
|
||||
for parent_name in parts {
|
||||
// find id from old_parts by this name
|
||||
let res_from_old = old_activities.iter().find(|ac| ac.name == parent_name);
|
||||
let res_from_new = new_activities.iter().find(|ac| ac.name == parent_name);
|
||||
let parent_activity = res_from_old.or(res_from_new).ok_or(mize_err!(
|
||||
"activity '{name}' declares parent '{parent_name}'"
|
||||
))?;
|
||||
parents_ids.push(parent_activity.id);
|
||||
}
|
||||
let child_activity = new_activities
|
||||
.iter()
|
||||
.find(|ac| ac.name == name)
|
||||
.ok_or(mize_err!(".."))?;
|
||||
let (wait_tx, wait_rx) = oneshot::channel::<()>();
|
||||
wait_revievers.push(wait_rx);
|
||||
con.reducers.update_activity_then(
|
||||
Activity {
|
||||
parents_ids,
|
||||
..child_activity.to_owned()
|
||||
},
|
||||
move |_, _| {
|
||||
let _ = wait_tx.send(());
|
||||
},
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
// wait for db stuff to finish
|
||||
for wait_rx in wait_revievers {
|
||||
wait_rx.recv().unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user