Skip to content

Commit

Permalink
Implement singularity by PID
Browse files Browse the repository at this point in the history
  • Loading branch information
suhlig committed Aug 29, 2021
1 parent 5bd1f7e commit 490e1da
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 53 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ rustomato break [start] # Starts a break. Auto-finishes the currently activ
* `pomodoro` and `break` will block until the time is over. If the command is interrupted with Control-C (`SIGINT`), the Pomodoro or break is finished immediately.
* Until we have hooks, here is how to use notifications:
```command
$ rustomato pomodoro start && terminal-notifier -message "Pomodoro is over" -title rustomato || terminal-notifier -message "Pomodoro cancelled" -title rustomato
$ rustomato pomodoro start && terminal-notifier -message "Pomodoro is over" -title rustomato -sound glass -group rustomato || terminal-notifier -message "Pomodoro cancelled" -title rustomato -sound glass -group rustomato
```

If you prefer tmux:
Expand All @@ -43,6 +43,8 @@ gh release create v0.0.2 --notes MVP

* Install and update rust with `rustup`
* Run: `cargo run -- pomodoro`
* Load the database schema with `sqlite3 ~/.rustomato.db < db/schema.sql`
- TODO Make this a proper migration; embedded into the binary
* Build a release: `cargo build --release` (binary found in `target/release/`)
* `rustomato pomodoro interrupt` sends `SIGUSR1` to the currently running `rustomato` process (use [signal-hook](https://crates.io/crates/signal-hook) for that)
* TODO Write a test that proves that there are no two active (Pomororo XOR Break) at the same time
Expand Down
22 changes: 20 additions & 2 deletions db/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ DROP TABLE IF EXISTS schedulables;
CREATE TABLE schedulables (
uuid TEXT NOT NULL PRIMARY KEY,
kind TEXT NOT NULL DEFAULT 'pomodoro',
pid INTEGER,
duration INTEGER,
started_at INTEGER NOT NULL,
finished_at INTEGER,
cancelled_at INTEGER,
CHECK ( pid >= 0 ),
CHECK ( kind == 'pomodoro' OR kind == 'break' ),
CHECK (
-- active
Expand All @@ -18,9 +20,23 @@ CREATE TABLE schedulables (
)
);

-- At any given time, only one schedulable may be in active state
-- Only one row may have a non-NULL pid
DROP TRIGGER IF EXISTS singularity_pid;
CREATE TRIGGER
singularity_pid
BEFORE INSERT ON
schedulables
BEGIN
SELECT CASE WHEN
(SELECT COUNT(*) FROM schedulables WHERE PID IS NOT NULL) > 0
THEN
RAISE(FAIL, "Cannot have two PIDs running at the same time")
END;
END;

-- Only one schedulable may be in active state
CREATE UNIQUE INDEX
singularity
singularity_state
ON
schedulables(started_at)
WHERE
Expand All @@ -38,6 +54,7 @@ AS
SELECT
kind,
uuid,
pid,
started_at
FROM
schedulables
Expand Down Expand Up @@ -82,6 +99,7 @@ AS
SELECT
kind,
uuid,
pid,
duration,
datetime(started_at, 'unixepoch', 'localtime') as started_at,
datetime(finished_at, 'unixepoch', 'localtime') as finished_at,
Expand Down
20 changes: 16 additions & 4 deletions db/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ main(){

reset
uuid="$(insert-active)"
# finish "$uuid"
insert-active # should fail unless $uuid was finished
finish "$uuid"
insert-active # must fail if $uuid was not finished yet
show-all

reset
insert-two-pids # should fail
}

reset() {
Expand All @@ -40,11 +43,19 @@ reset() {
insert-active() {
header "${FUNCNAME[0]}"
uuid=$(generate-uuid)
echo "INSERT INTO schedulables (uuid, duration, started_at) VALUES ('$uuid', 25, strftime('%s','now'));" | database
echo "INSERT INTO schedulables (pid, uuid, duration, started_at) VALUES ($RANDOM,'$uuid', 25, strftime('%s','now'));" | database
>&2 echo "Inserted $uuid"
echo "$uuid"
}

insert-two-pids() {
header "${FUNCNAME[0]}"
uuid=$(generate-uuid)
echo "INSERT INTO schedulables (pid, uuid, duration, started_at) VALUES ($RANDOM,'$uuid', 25, strftime('%s','now'));" | database
uuid=$(generate-uuid)
echo "INSERT INTO schedulables (pid, uuid, duration, started_at) VALUES ($RANDOM,'$uuid', 25, strftime('%s','now'));" | database
}

insert-finished() {
header "${FUNCNAME[0]}"
echo "INSERT INTO schedulables (
Expand Down Expand Up @@ -116,6 +127,7 @@ cancel() {
show-all() {
header "${FUNCNAME[0]}"
echo "SELECT
pid,
kind,
uuid,
datetime(started_at, 'unixepoch', 'localtime') as started_at,
Expand All @@ -142,7 +154,7 @@ show-cancelled() {
}

database() {
sqlite3 -header -separator ' | ' ~/.rustomato.sqlite3
sqlite3 -header -separator ' | ' ~/.rustomato.db
}

header() {
Expand Down
36 changes: 22 additions & 14 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
mod rustomato;

use std::{env,process};
use std::path::*;
use clap::{crate_version, AppSettings, Clap};
use rustomato::persistence::Repository;
use rustomato::scheduling::Scheduler;
use rustomato::{Schedulable, Status, Kind};
use std::path::Path;
use std::process;
use rustomato::{Kind, Schedulable, Status};

/// A simple Pomodoro timer for the command line
#[derive(Clap)]
Expand Down Expand Up @@ -95,19 +95,27 @@ struct StartBreak {
struct FinishBreak {}

fn main() {
// TODO This is ugly
let home = dirs::home_dir().expect("Unable to find home directory");
let home_home = home.to_str().expect("Unable to convert to string");
let location = Path::new(home_home).join(".rustomato.sqlite3");
let location = match env::var("DATABASE_URL") {
Ok(val) => PathBuf::from(val),
Err(_) => {
let mut home = dirs::home_dir().expect("Unable to resolve home directory");
home.push(".rustomato.db");
home.to_path_buf()
}
};

println!("Using database {:?}", location); // TODO Only if verbose

let repo = Repository::new(&location);
let scheduler = Scheduler::new(repo);
let pid = process::id();

match Opts::parse().subcmd {
SubCommands::Pomodoro(pomodoro_options) => match pomodoro_options.subcmd {
PomodoroCommands::Start(start_pomodoro_options) => {
let pom = Schedulable::new(Kind::Pomodoro, start_pomodoro_options.duration.into());
println!("Starting new {}", pom); // TODO Only if verbose
let pom =
Schedulable::new(pid, Kind::Pomodoro, start_pomodoro_options.duration.into());
println!("Starting {}", pom); // TODO Only if verbose

let result = scheduler.run(pom);

Expand All @@ -126,21 +134,21 @@ fn main() {
}
}
Err(err) => {
println!("Failed to schedule {}", err); // TODO Only if verbose
eprintln!("Failed to schedule Pomodoro; {}", err);
process::exit(1);
}
}
}
PomodoroCommands::Interrupt(_) => {
println!("TODO Marking the active Pomodoro as interrupted");
eprintln!("TODO Marking the active Pomodoro as interrupted");
}
PomodoroCommands::Annotate(_) => {
println!("TODO Annotating the active Pomodoro");
eprintln!("TODO Annotating the active Pomodoro");
}
},
SubCommands::Break(break_options) => match break_options.subcmd {
BreakCommands::Start(start_break_options) => {
let br3ak = Schedulable::new(Kind::Break, start_break_options.duration.into());
let br3ak = Schedulable::new(pid, Kind::Break, start_break_options.duration.into());

println!("Starting {}", br3ak); // TODO Only if verbose

Expand All @@ -152,7 +160,7 @@ fn main() {
process::exit(0);
}
Err(err) => {
println!("Failed to schedule: {}", err); // TODO Only if verbose
eprintln!("Failed to schedule break; {}", err);
process::exit(1);
}
}
Expand Down
37 changes: 31 additions & 6 deletions src/rustomato.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@ pub enum Kind {
Break,
}

// Neither Pomodoro nor Break
#[derive(Debug)]
pub struct UnknownKind {
offender: String
}

pub struct Schedulable {
pid: u32,
kind: Kind,
uuid: Uuid,
duration: u64,
Expand All @@ -26,9 +33,20 @@ pub enum Status {
Finished,
}

impl Kind {
pub fn from(str: String) -> Result<Self, UnknownKind> {
match str.to_lowercase().as_str() {
"pomodoro" => Ok(Kind::Pomodoro),
"break" => Ok(Kind::Break),
_ => Err(UnknownKind{offender: str}),
}
}
}

impl Schedulable {
pub fn new(kind: Kind, duration: u64) -> Self {
pub fn new(pid: u32, kind: Kind, duration: u64) -> Self {
Self {
pid: pid,
kind: kind,
uuid: Uuid::new_v4(),
duration: duration,
Expand All @@ -55,15 +73,22 @@ impl fmt::Display for Kind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Kind::Pomodoro => {
write!(f, "Pomodoro")
write!(f, "pomodoro")
}
Kind::Break => {
write!(f, "Break")
write!(f, "break")
}
}
}
}

impl std::fmt::Debug for Kind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Kind")
.finish()
}
}

impl ToSql for Kind {
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
match self {
Expand All @@ -80,21 +105,21 @@ impl fmt::Display for Schedulable {
write!(
f,
"{} {}; active since {}",
self.kind, self.uuid, self.started_at
self.kind, self.uuid, self.started_at // TODO print prettier timestamp
)
}
Status::Cancelled => {
write!(
f,
"{} {}; cancelled at {}",
self.kind, self.uuid, self.cancelled_at
self.kind, self.uuid, self.cancelled_at // TODO print prettier timestamp
)
}
Status::Finished => {
write!(
f,
"{} {}; finished at {}",
self.kind, self.uuid, self.finished_at
self.kind, self.uuid, self.finished_at // TODO print prettier timestamp
)
}
}
Expand Down
Loading

0 comments on commit 490e1da

Please sign in to comment.