Skip to content

Commit

Permalink
0.5.0: Timeout/waitfor are now threaded and work together, silence lsof
Browse files Browse the repository at this point in the history
  • Loading branch information
Randomblock1 committed Mar 8, 2023
1 parent 904ff0f commit 89e9ec3
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 55 deletions.
30 changes: 15 additions & 15 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "caffeinate2"
version = "0.4.3"
version = "0.5.0"
edition = "2021"
authors = ["Randomblock1 <[email protected]>"]
description = "Caffeinate MacOS utility with more options. Temporarily prevent your system from sleeping."
Expand All @@ -14,7 +14,7 @@ categories = ["command-line-utilities"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap = { version = "4.1.6", features = ["derive", "wrap_help"] }
clap = { version = "4.1.8", features = ["derive", "wrap_help"] }
core-foundation = "0.9.3"
libloading = "0.7.4"
nix = "0.26.2"
Expand Down
27 changes: 19 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

## Current Status

In development. Works fine, but I want to add more features and document the different types of sleep before version 1.0.0.
In development. Works fine, but I want to add more features and document the different types of sleep before version
1.0.0.

## Installation

Expand All @@ -16,7 +17,7 @@ Download the latest release from [here](https://github.com/randomblock1/caffeina

### Homebrew

_This won't be availible until version 1.0.0._
_This won't be available until version 1.0.0._

### Cargo

Expand Down Expand Up @@ -50,22 +51,30 @@ Options:

### Command

Sleep disabled until the command completes. You should enclose the command in quotes. Timeout and PID will be ignored if a command is specified.
Sleep disabled until the command completes. You should enclose the command in quotes, although sometimes it isn't
required. Timeout and PID will be ignored if a command is specified.

`caffeinate2 "echo hello"`
`caffeinate2 "sleep 5"`

### Timeout and PID

Sleep disabled for a certain amount of time or until program with the specified PID completes. If both timeout and PID are specified, whichever was specified first will be used.
Sleep is disabled for a certain amount of time or until the program with the specified PID completes. If both are
specified, it waits until one of them completes.

Timeout can either be a number of seconds or a duration string. For example you can pass `-t 600` or `-t 10m` to wait for 10 minutes. You can create more descriptive durations, like `-t "1 hour and 30 minutes"`, but it only looks at the first letter (so "3 movies" is just 3 minutes). Anything that's not a number followed by a letter will be ignored (the "and" in the previous example). **YOU MUST USE QUOTATION MARKS FOR THIS TO WORK.** Otherwise it will try to parse anything that's past the space as a command, and ignore the timeout.
Timeout can either be a number of seconds or a duration string. For example, you can pass `-t 600` or `-t 10m` to wait
for 10 minutes. You can create more descriptive durations, like `-t "1 hour and 30 minutes"`, but it only looks at the
first letter (so "3 movies" is just 3 minutes). Anything that's not a number followed by a letter will be ignored (the "
and" in the previous example). **YOU MUST USE QUOTATION MARKS FOR THIS TO WORK.** Otherwise, it will try to parse
anything that's past the space as a command, and ignore the timeout.

`caffeinate2 -t 600`

`caffeinate2 -t "1 hour and 30 minutes"`

`caffeinate2 -w 1234`

`caffeinate2 -t 600 -w 1234`

### None of the above

Sleep will be disabled until you press `Ctrl+C`.
Expand All @@ -78,8 +87,10 @@ This project is licensed under the MIT License - see [the license file](LICENSE.

## TODO

- [ ] Treat timeout like a timeout, and not a timer.
- [x] Make timeout and PID work together
- [ ] Figure out how to fake a tty (for example, `caffeinate2 brew list` is uncolored)
- [ ] Print sleep types better
- [ ] Document all the sleep types (they are somewhat vague)
- [x] Get system sleep status without reading a plist
- [x] Get PID info by using Grand Central Dispatch instead of a weird `lsof` hack - POSTPONED because it's a mess and it currently Just Works™️
- [x] Get PID info by using Grand Central Dispatch instead of a weird `lsof` hack - POSTPONED because it's a mess, and
it currently Just Works™️
88 changes: 58 additions & 30 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use signal_hook::{consts::SIGINT, iterator::Signals};
use std::io;
use std::os::unix::process::CommandExt;
use std::process;
use std::sync::mpsc::channel;
use std::thread;
use time::macros::format_description;

Expand Down Expand Up @@ -269,28 +270,27 @@ fn main() {
exit_code = child.wait().unwrap().code().unwrap_or(0);
} else if args.timeout.is_some() || args.waitfor.is_some() {
// If timeout or waitfor is used, wait appropriately
// The original caffeinate treats arg position as priority
let args_vec = std::env::args().collect::<Vec<_>>();
let timeout_index = args_vec
.iter()
.position(|x| x == "--timeout" || x == "-t")
.unwrap_or(std::usize::MAX);
let waitfor_index = args_vec
.iter()
.position(|x| x == "--waitfor" || x == "-w")
.unwrap_or(std::usize::MAX);
if timeout_index < waitfor_index {
// TODO Major Refactor Needed: They can be used at the same time, but the code is a mess

let (sender, receiver) = channel();

let mut duration = std::time::Duration::from_secs(0);
let mut end_time = time::OffsetDateTime::now_local().unwrap();

let timeout = args.timeout.is_some();
let waitfor = args.waitfor.is_some();
if timeout {
// Timeout selected
// Print how long we're waiting for
let duration = std::time::Duration::from_secs(parse_duration(args.timeout.unwrap()));
let end_time = time::OffsetDateTime::now_local().unwrap() + duration;
duration = std::time::Duration::from_secs(parse_duration(args.timeout.unwrap()));
end_time = time::OffsetDateTime::now_local().unwrap() + duration;
let seconds = duration.as_secs() % 60;
let minutes = (duration.as_secs() % 3600) / 60;
let hours = (duration.as_secs() % 86400) / 3600;
let days = duration.as_secs() / 86400;

sleep_str += &format!(
"for {}{}{}{}.",
"for {}{}{}{}",
if days > 0 {
format!("{} day{} ", days, if days != 1 { "s" } else { "" })
} else {
Expand All @@ -316,8 +316,19 @@ fn main() {
String::from("")
}
);
println!("{sleep_str}");
}

print!("{sleep_str}");

if timeout && waitfor {
print!(" or ");
}
if waitfor {
print!("until PID {} finishes", args.waitfor.unwrap());
}
println!(".");

if timeout {
let short_fmt = format_description!("at [hour repr:12]:[minute]:[second] [period]");
let long_fmt = format_description!(
"on [month repr:long] [day] at [hour repr:12]:[minute]:[second] [period]"
Expand All @@ -332,34 +343,51 @@ fn main() {
end_time.format(&short_fmt).unwrap()
}
);
thread::sleep(duration);
} else {

// Spawn a new thread to handle the timeout
let timeout_sender = sender.clone();
thread::spawn(move || {
thread::sleep(duration);
timeout_sender.send(0).unwrap();
});
}

if waitfor {
// Wait for PID selected
let pid = args.waitfor.unwrap();
sleep_str += &format!("until PID {pid} finishes.");
println!("{sleep_str}");

let mut child = process::Command::new("lsof")
.arg("-p")
.arg(pid.to_string())
.arg("+r")
.arg("1")
.stdout(process::Stdio::null())
.stderr(process::Stdio::null())
.spawn()
.unwrap();

let status = child.wait().unwrap();

if status.code() == Some(1) {
eprintln!("PID {} does not exist.", pid);
exit_code = 1;
} else {
print!("PID {pid} finished ");
let now = time::OffsetDateTime::now_local().unwrap();
let short_fmt = format_description!("at [hour repr:12]:[minute]:[second] [period]");
println!("{}", now.format(&short_fmt).unwrap());
}
// Spawn a new thread to handle the timeout
let waitpid_sender = sender.clone();
thread::spawn(move || {
let status = child.wait().unwrap();
let exit_code;

if status.code() == Some(1) {
eprintln!("PID {} does not exist.", pid);
exit_code = 1;
} else {
print!("PID {pid} finished ");
exit_code = 0;
let now = time::OffsetDateTime::now_local().unwrap();
let short_fmt = format_description!("at [hour repr:12]:[minute]:[second] [period]");
println!("{}", now.format(&short_fmt).unwrap());
}
waitpid_sender.send(exit_code).unwrap();
});
}

// Wait for either the timeout or the process to finish
exit_code = receiver.recv().unwrap();
} else {
// If no timer arguments are provided, disable sleep until Ctrl+C is pressed
sleep_str += "until Ctrl+C pressed.";
Expand Down

0 comments on commit 89e9ec3

Please sign in to comment.