add pause, pidfile
This commit is contained in:
+128
-12
@@ -1,4 +1,8 @@
|
||||
use std::ffi::OsString;
|
||||
use std::fs::{self, read_to_string};
|
||||
use std::io::{stdout, Stdout, Write};
|
||||
use std::os::unix::prelude::OsStringExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -10,16 +14,28 @@ use crossterm::{
|
||||
ExecutableCommand, Result,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use tokio::sync::broadcast::{channel, Receiver, Sender};
|
||||
use tokio::time::sleep;
|
||||
|
||||
const ONE_SECOND: Duration = Duration::from_secs(1);
|
||||
const PID_FILE: &str = "/tmp/pomodoro.pid";
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> Result<()> {
|
||||
let timer = Pomodoro::new();
|
||||
let pid = if let Some(pid) = Pid::check_already_running() {
|
||||
eprintln!("already running (pid {})", pid.pid);
|
||||
return Ok(());
|
||||
} else {
|
||||
Pid::create()
|
||||
};
|
||||
|
||||
let (tx, rx) = channel::<Message>(10);
|
||||
|
||||
let timer = Pomodoro::new(rx);
|
||||
let ui = Ui::new(tx);
|
||||
|
||||
let timer_task = tokio::spawn(async move { timer.main_loop().await });
|
||||
let ui_task = tokio::spawn(async move { Ui::main_loop().await });
|
||||
let ui_task = tokio::spawn(async move { ui.main_loop().await });
|
||||
|
||||
tokio::select! (
|
||||
result = timer_task => result??,
|
||||
@@ -28,13 +44,67 @@ async fn main() -> Result<()> {
|
||||
|
||||
Ui::clear_terminal()?;
|
||||
|
||||
pid.remove();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct Ui;
|
||||
struct Pid {
|
||||
pid: u32,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl Pid {
|
||||
fn check_already_running() -> Option<Self> {
|
||||
let path = PathBuf::from(PID_FILE);
|
||||
Self::get_pid_from_file(&path)
|
||||
.and_then(Self::check_process_exists)
|
||||
.map(|pid| Self { pid, path })
|
||||
}
|
||||
|
||||
fn check_process_exists(pid: u32) -> Option<u32> {
|
||||
let mut path = PathBuf::from("/proc");
|
||||
path.push(pid.to_string());
|
||||
path.push("cmdline");
|
||||
let content = fs::read(path).ok()?;
|
||||
let cmdline = OsString::from_vec(content).to_string_lossy().to_string();
|
||||
if cmdline.contains("pomodoro") {
|
||||
Some(pid)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_pid_from_file(file: &Path) -> Option<u32> {
|
||||
let content = read_to_string(file).ok()?;
|
||||
let line = content.lines().nth(0)?;
|
||||
let result: u32 = line.parse().ok()?;
|
||||
Some(result)
|
||||
}
|
||||
|
||||
fn create() -> Self {
|
||||
let pid = std::process::id();
|
||||
let path = PathBuf::from(PID_FILE);
|
||||
let mut fd = fs::File::create(&path).unwrap();
|
||||
write!(fd, "{pid}").unwrap();
|
||||
Self { pid, path }
|
||||
}
|
||||
|
||||
fn remove(self) {
|
||||
fs::remove_file(self.path).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
struct Ui {
|
||||
sender: Sender<Message>,
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
async fn main_loop() -> Result<()> {
|
||||
fn new(sender: Sender<Message>) -> Self {
|
||||
Self { sender }
|
||||
}
|
||||
|
||||
async fn main_loop(self) -> Result<()> {
|
||||
let mut reader = EventStream::new();
|
||||
|
||||
loop {
|
||||
@@ -42,11 +112,15 @@ impl Ui {
|
||||
stdout().execute(cursor::Hide)?;
|
||||
|
||||
if let Some(Ok(Event::Key(key_event))) = reader.next().await {
|
||||
if Self::is_char_q(key_event) || Self::is_ctrl_c(&key_event) {
|
||||
if Self::is_char_q(key_event) || Self::is_ctrl_c(key_event) {
|
||||
self.sender.send(Message::Quit).unwrap();
|
||||
disable_raw_mode()?;
|
||||
stdout().execute(cursor::Show)?;
|
||||
|
||||
return Ok(());
|
||||
};
|
||||
if Self::is_space(key_event) {
|
||||
self.sender.send(Message::TogglePause).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,7 +130,11 @@ impl Ui {
|
||||
key_event.code == KeyCode::Char('q')
|
||||
}
|
||||
|
||||
fn is_ctrl_c(key_event: &crossterm::event::KeyEvent) -> bool {
|
||||
fn is_space(key_event: crossterm::event::KeyEvent) -> bool {
|
||||
key_event.code == KeyCode::Char(' ')
|
||||
}
|
||||
|
||||
fn is_ctrl_c(key_event: crossterm::event::KeyEvent) -> bool {
|
||||
key_event.code == KeyCode::Char('c') && key_event.modifiers.contains(KeyModifiers::CONTROL)
|
||||
}
|
||||
|
||||
@@ -75,10 +153,12 @@ struct Pomodoro {
|
||||
intervals: [Interval; 8],
|
||||
remaining: Duration,
|
||||
stdout: Stdout,
|
||||
receiver: Receiver<Message>,
|
||||
paused: bool,
|
||||
}
|
||||
|
||||
impl Pomodoro {
|
||||
fn new() -> Self {
|
||||
fn new(receiver: Receiver<Message>) -> Self {
|
||||
let intervals = Interval::default_sequence();
|
||||
let interval_idx = 0;
|
||||
let remaining = intervals[interval_idx].get_duration();
|
||||
@@ -88,6 +168,8 @@ impl Pomodoro {
|
||||
intervals,
|
||||
remaining,
|
||||
stdout: stdout(),
|
||||
receiver,
|
||||
paused: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,16 +184,43 @@ impl Pomodoro {
|
||||
)?;
|
||||
self.stdout.queue(MoveTo(0, 1))?;
|
||||
write!(self.stdout, "remaining: {}", format(self.remaining))?;
|
||||
if self.paused {
|
||||
write!(self.stdout, " (paused)")?;
|
||||
}
|
||||
self.stdout.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn toggle_pause(&mut self) {
|
||||
self.paused ^= true;
|
||||
}
|
||||
|
||||
async fn main_loop(mut self) -> Result<()> {
|
||||
loop {
|
||||
'inner: loop {
|
||||
self.show_status()?;
|
||||
sleep(ONE_SECOND).await;
|
||||
self.remaining -= ONE_SECOND;
|
||||
|
||||
if self.paused {
|
||||
if self.receiver.recv().await.unwrap() == Message::TogglePause {
|
||||
self.toggle_pause();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
tokio::select! (
|
||||
_ = sleep(ONE_SECOND) => {self.remaining -= ONE_SECOND;}
|
||||
message = self.receiver.recv() => {
|
||||
if let Ok(m) = message{
|
||||
match m {
|
||||
Message::Quit => {return Ok(());},
|
||||
Message::TogglePause => {
|
||||
self.toggle_pause();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if self.remaining.is_zero() {
|
||||
self.show_status()?;
|
||||
@@ -121,7 +230,7 @@ impl Pomodoro {
|
||||
|
||||
self.next_interval();
|
||||
self.remaining = self.current_interval().get_duration();
|
||||
self.send_notification(self.current_interval().get_message());
|
||||
self.send_notification(self.current_interval().get_message())?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,10 +243,11 @@ impl Pomodoro {
|
||||
self.intervals[self.interval_idx]
|
||||
}
|
||||
|
||||
fn send_notification(&self, message: &str) {
|
||||
fn send_notification(&self, message: &str) -> Result<()> {
|
||||
let mut command = Command::new("notify-send");
|
||||
command.args(["-t", "0", message]);
|
||||
command.status().unwrap();
|
||||
command.status()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,3 +307,9 @@ fn format(dur: Duration) -> String {
|
||||
|
||||
format!("{min}:{sec:0>2}")
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum Message {
|
||||
Quit,
|
||||
TogglePause,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user