tagtimepy/prompter.py

220 lines
8.3 KiB
Python

import os
import time
import logging
import subprocess
import tempfile
from typing import List
from tagtimerc import TagTimeRc
from timer import Timer
from ping import Ping
from taglog import TagLog
class Prompter:
"""
Prompter tries to replicate the behavior of the original TagTime Perl
implementation.
"""
def __init__(self, tagtimerc: TagTimeRc, gui: bool = False):
self.rc = tagtimerc
self.timer = Timer(self.rc.seed, self.rc.urping, self.rc.gap)
self.taglog = TagLog(self.rc.log_file)
self.gui = gui
def _get_prompt(self, time: int, previous_tags: str) -> str:
"""Create prompt string for terminal mode."""
age = self.timer.time_now() - time
time_str = Ping.add_time_annotation(time, "", 13).strip()
if age < 0:
prompt_str = "You are from the future? Good job!\n" \
"What will you be doing then? {}\n"
elif age < 10:
prompt_str = "It's tag time! What are you doing RIGHT NOW {}?\n"
else:
warning = "WARNING This popup is {} seconds late WARNING\n".format(
age)
len_warning = len(warning) - 1
prompt_str = "-" * len_warning + "\n"
prompt_str += warning
prompt_str += "-" * len_warning + "\n"
prompt_str += "It's tag time! What were you doing {}?\n"
prompt_str = prompt_str.format(time_str)
if previous_tags:
m = "Ditto (\") to repeat prev tags: {}\n".format(previous_tags)
prompt_str += m
prompt_str += "> "
return prompt_str
def ping(self, time: int, previous_tags: str = ""):
"""Ask the user for tags and log them."""
prompt_str = self._get_prompt(time, previous_tags)
input_str = input(prompt_str)
if input_str == '"':
tags = previous_tags.split()
else:
tags = input_str.split()
p = Ping(time, tags, [])
self.taglog.log_ping(p, True, self.rc.linelen)
def _get_next_ping_time(self) -> int:
"""
Figure out the next ping after the last one that's in the log file.
A ping is on schedule if it was generated from the seed/urping pair
used by self.timer and off schedule, otherwise.There are three cases.
1. If no ping exists yet, next_ping_time is the latest time on schedule
before launch time.
2. If the last ping is on schedule, next_ping_time is the time after
the time of the last ping.
3. If the last ping is not on schedule, next_ping_time is the next
time after the last ping that is on schedule.
Note that the third case is different from the original implementation:
$nxtping = prevping($launchTime); # original implementation in Perl
$nxtping = nextping($lstping); # this implementation in Perl
The original implementation could have the effect that next_ping has a
lower value than last_ping which means the resulting pings would not be
sorted by ascending time-stamp anymore.
"""
launch_time = self.timer.time_now()
if not self.taglog.exists():
next_time = self.timer.prev(launch_time)
else:
last_ping = self.taglog.last_ping()
last_time = last_ping.time
self.timer.prev(last_time)
next_time = self.timer.next()
if last_time == next_time:
next_time = self.timer.next()
else:
line = last_ping.line.strip()
m = "{}".format(line)
logging.warning(m)
return next_time
def _call(self, args: List[str]):
"""
Create subprocess with the provided args. Use PIPEs to avoid "read from
shell input/output error" leaking into the stderr of this process.
"""
subprocess.call(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def _prompt_and_log_xt(self, next_time: int):
"""
Open a second instance of TagTime with the ping command to prompt the
user for their current tags. The response is written into a temporary
file. Read out the temporary file here and log the ping into the main
log file.
"""
# Create a temporary file and get it's name. Then delete the object so
# that the other process can access the file. According to the
# documentation it depends on the platform whether the file could be
# accessed from other processes while it is open [1].
# [1] https://docs.python.org/3.8/library/tempfile.html
temp_file = tempfile.NamedTemporaryFile()
temp_file_name = temp_file.name
del temp_file
args = self.rc.xt.split() # Launch terminal
args += ["-T", "TagTimer"] # Title is TagTime
args += ["-e"] + self.rc.tagtimecmd # Execute tagtimecmd in terminal
args += ["ping"] # Ping command
args += ["--time", str(next_time)] # Log time
args += ["--log_file"] # Explicitely provide log file
args += [temp_file_name] # Log into the temporary file
args += ["--previous_tags"] # Provide perevious tags for ditto
args += [" ".join(self.taglog.last_tags())]
self._call(args)
# Read the ping from the temporary file
ping = None
try:
with open(temp_file_name, "r") as f:
line = f.read()
ping = Ping.line_to_ping(line)
os.remove(temp_file_name)
except EnvironmentError:
logging.warning("Cannot open temporary file. Log error.")
except AttributeError:
logging.warning("Could not read Ping from temporary file.")
if ping:
# If we got a ping log the line (don't mess with the order of the
# tags/comments.
self.taglog.log_line(ping.line)
else:
# Otherwise, create a new ping object with error tags.
ping = Ping(next_time, self.rc.tags_err, [])
self.taglog.log_ping(ping, True, self.rc.linelen)
def _prompt_and_log(self, next_time: int):
if self.gui:
self._prompt_and_log_tk(next_time)
else:
self._prompt_and_log_xt(next_time)
def _prompt_and_log_tk(self, next_time: int):
"""Open GUI prompt via TK and log tags."""
import tkinter as tk
from tkinter import simpledialog
root = tk.Tk()
root.withdraw()
previous_tags = " ".join(self.taglog.last_tags())
prompt_str = self._get_prompt(next_time, previous_tags)
input_str = simpledialog.askstring(title="TagTime",
prompt=prompt_str)
if input_str == "":
tags = self.rc.tags_err
elif input_str == '"':
tags = previous_tags.split()
else:
tags = input_str.split()
ping = Ping(next_time, tags, [])
self.taglog.log_ping(ping, True, self.rc.linelen)
def _open_editor(self):
if not self.rc.ed:
return
args = self.rc.ed.split() # Launch terminal
args += [self.rc.log_file] # and open the log file
self._call(args)
def daemon(self):
next_time = self._get_next_ping_time()
launch_time = self.timer.time_now()
open_editor = False
# If we missed any pings by more than $retrothresh seconds for no
# apparent reason, then assume the computer was off and auto-log them.
while next_time < (launch_time - self.rc.retrothresh):
p = Ping(next_time, self.rc.tags_off, [])
self.taglog.log_ping(p, True, self.rc.linelen)
next_time = self.timer.next()
open_editor = True
while True:
now = self.timer.time_now()
if next_time < (now - self.rc.retrothresh):
p = Ping(next_time, self.rc.tags_afk, [])
self.taglog.log_ping(p, True, self.rc.linelen)
next_time = self.timer.next()
open_editor = True
elif next_time < now:
self._prompt_and_log(next_time)
next_time = self.timer.next()
else:
if open_editor:
self._open_editor()
open_editor = False
time.sleep(30)