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)