220 lines
8.3 KiB
Python
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)
|