aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlonkaars <loek@pipeframe.xyz>2024-03-12 09:02:24 +0100
committerlonkaars <loek@pipeframe.xyz>2024-03-12 09:02:24 +0100
commitd3b396b17602ad71a62cbc2eaf595a1d94579c4a (patch)
tree3be5e49c5d9081ac3de4cd34b64b47ada23e9a22
parente6c1cacb60010ec75e02cf72acf34278417670fd (diff)
make state update atomic and clean up some code
-rw-r--r--core/config2
-rwxr-xr-xcore/help3
-rw-r--r--core/lib19
-rwxr-xr-xcore/pause2
-rwxr-xr-xcore/reset8
-rwxr-xr-xcore/start3
-rwxr-xr-xcore/state2
-rw-r--r--core/update52
8 files changed, 77 insertions, 14 deletions
diff --git a/core/config b/core/config
index 34c96b8..aedd4f3 100644
--- a/core/config
+++ b/core/config
@@ -1,4 +1,5 @@
#!/bin/sh
+
# initialize default configuration values
export POMODORO_STATE_PATH="${POMODORO_STATE_PATH:-$XDG_CACHE_HOME/$progname}" # ~/.cache/dppt
export POMODORO_NORMAL_DURATION="${POMODORO_NORMAL_DURATION:-$(( 25 * 60 ))}" # 25 minutes
@@ -6,3 +7,4 @@ export POMODORO_BREAK_SHORT_DURATION="${POMODORO_BREAK_SHORT_DURATION:-$(( 5 * 6
export POMODORO_BREAK_LONG_DURATION="${POMODORO_BREAK_LONG_DURATION:-$(( 15 * 60 ))}" # 15 minutes
export POMODORO_BREAK_SHORT_INTERVAL="${POMODORO_BREAK_INTERVAL:-2}" # every other lap
export POMODORO_BREAK_LONG_INTERVAL="${POMODORO_BREAK_INTERVAL:-6}" # every 3rd break
+
diff --git a/core/help b/core/help
index 98abd1e..4bc5496 100755
--- a/core/help
+++ b/core/help
@@ -1,5 +1,5 @@
#!/bin/sh
-[ "$1" = "info" ] && echo "show this very message" && exit 2
+[ "$1" = "info" ] && echo "show help" && exit 2
cat << EOF
$progname -- daemonless posix pomodoro timer
@@ -35,3 +35,4 @@ cat << EOF
if no action is provided, the \`state\` action is chosen by default
EOF
+
diff --git a/core/lib b/core/lib
index 9892663..21c64f9 100644
--- a/core/lib
+++ b/core/lib
@@ -16,3 +16,22 @@ err() {
# debug:
# bc() { tee /dev/stderr | /usr/bin/bc ; }
+fmt_time() {
+ ss="$(echo "$1 / 1" | bc)"
+ mm="$(echo "$ss / 60" | bc)"
+ ss="$(echo "$ss % 60" | bc)"
+ hh="$(echo "$mm / 60" | bc)"
+ mm="$(echo "$mm % 60" | bc)"
+ _time_fmt
+}
+
+_time_fmt() { # [HH:]MM:SS
+ [ $hh -gt 0 ] && printf '%02d:' $hh
+ printf "%02d:%02d\n" $mm $ss
+}
+
+# _time_fmt() { # [HHh]MMmSSs
+# [ $hh -gt 0 ] && printf '%02dh' $hh
+# printf "%02dm%02ds\n" $mm $ss
+# }
+
diff --git a/core/pause b/core/pause
index a55bd30..3037c1c 100755
--- a/core/pause
+++ b/core/pause
@@ -6,7 +6,7 @@
[ "$state" != 'running' ] && err "timer is not running"
-time="$("$time - $now" | bc)"
+time="$(echo "$time - $now" | bc)"
state='paused'
save_state
diff --git a/core/reset b/core/reset
index 5492dce..a4c245e 100755
--- a/core/reset
+++ b/core/reset
@@ -1,5 +1,11 @@
#!/bin/sh
[ "$1" = "info" ] && echo "reset the timer and lap counter" && exit 2
-rm -rf "$POMODORO_STATE_PATH"
+lap=0
+state='reset'
+update_time=1
+
+. "$core_path/update"
+
+save_state
diff --git a/core/start b/core/start
index ae471ad..dbbabc0 100755
--- a/core/start
+++ b/core/start
@@ -28,8 +28,8 @@ done
if [ "$state" = 'running' ] ; then
[ $allow_skip -eq 0 ] && err "timer is already running, use -s to skip lap"
- time="0.0"
lap=$(( $lap + 1 ))
+ update_time=1
. "$core_path/update"
fi
@@ -37,3 +37,4 @@ time="$(echo "$now + $time" | bc)"
state='running'
save_state
+
diff --git a/core/state b/core/state
index fcf327d..580afc6 100755
--- a/core/state
+++ b/core/state
@@ -17,5 +17,5 @@ EOF
}
. "$core_path/update"
-echo "[$state] $lap: ${remaining}s"
+echo "lap $lap, $state, $(fmt_time $remaining)"
diff --git a/core/update b/core/update
index 04fd29c..fbe0e86 100644
--- a/core/update
+++ b/core/update
@@ -1,13 +1,12 @@
#!/bin/sh
export now="$(date +%s.%N)"
-update_time=0
-
+# load current state
mkdir -p "$POMODORO_STATE_PATH"
load_or_init() {
property="$1"
default_value="$2"
- property_path="$POMODORO_STATE_PATH/$property"
+ property_path="$POMODORO_STATE_PATH/current/$property"
if [ -f "$property_path" ] ; then
eval $property='"$(cat "$property_path")"'
else
@@ -17,7 +16,13 @@ load_or_init() {
[ -z "$lap" ] && load_or_init lap 0
[ -z "$state" ] && load_or_init state 'reset'
[ -z "$time" ] && load_or_init time 0.0
-[ "$time" = "0.0" ] && update_time=1
+
+# save state (for diff)
+diff_state() { printf '%s:' "$lap" "$state" "$time" ; }
+original_state="$(diff_state)"
+
+# allow overriding this value manually
+[ -z "$update_time" ] && update_time=0
# calculate remaining time on timer
if [ -z "$remaining" ] ; then
@@ -34,6 +39,7 @@ fi
# update remaining time if in reset
[ "$state" = 'reset' ] && update_time=1
+[ "$time" = "0.0" ] && update_time=1
if [ $update_time -eq 1 ] ; then
time="$POMODORO_NORMAL_DURATION"
@@ -42,12 +48,40 @@ if [ $update_time -eq 1 ] ; then
remaining="$time"
fi
-# save state
save_state() {
- mkdir -p "$POMODORO_STATE_PATH"
- echo "$lap" > "$POMODORO_STATE_PATH/lap"
- echo "$state" > "$POMODORO_STATE_PATH/state"
- echo "$time" > "$POMODORO_STATE_PATH/time"
+ # do not save files if state didn't change
+ [ "$original_state" = "$(diff_state)" ] && return
+
+ # The state update needs to be atomic since other instances may access the
+ # state during an update. This is accomplished by replacing a single symlink,
+ # which should be atomic on most POSIX-compatible platforms, and is less
+ # complicated than a lockfile. $POMODORO_STATE_PATH contains three items:
+ #
+ # 1. current -- a symlink to `primary` or `secondary`
+ # 2. primary -- folder containing current state*
+ # 3. secondary -- temporary folder used write new state
+ #
+ # * `primary` will only contain stale state during an update (i.e. while
+ # `current` is pointing to `secondary`)
+
+ mkdir -p "$POMODORO_STATE_PATH/primary" "$POMODORO_STATE_PATH/secondary"
+
+ echo "$lap" > "$POMODORO_STATE_PATH/secondary/lap"
+ echo "$state" > "$POMODORO_STATE_PATH/secondary/state"
+ echo "$time" > "$POMODORO_STATE_PATH/secondary/time"
+
+ ln -sf "secondary" "$POMODORO_STATE_PATH/secondary/current"
+
+ # this is the atomic update that replaces the `current` symlink with the new
+ # state
+ mv "$POMODORO_STATE_PATH/secondary/current" "$POMODORO_STATE_PATH"
+
+ # next, replace the old `primary` state with the new state from `secondary`,
+ # and update the symlink a second time to now point to `primary` again (this
+ # does not change the state)
+ cp -r "$POMODORO_STATE_PATH/secondary/." "$POMODORO_STATE_PATH/primary/"
+ ln -sf "primary" "$POMODORO_STATE_PATH/primary/current"
+ mv "$POMODORO_STATE_PATH/primary/current" "$POMODORO_STATE_PATH"
}
# export state variables