Working on voice command logic.
[kiosk.git] / renderer.py
1 #!/usr/bin/env python3
2
3 from abc import ABC, abstractmethod
4 from datetime import datetime
5 from decorators import invocation_logged
6 import time
7 from typing import Dict, List, Optional, Set
8
9
10 class renderer(ABC):
11     """Base class for something that can render."""
12
13     @abstractmethod
14     def render(self):
15         pass
16
17     @abstractmethod
18     def get_name(self):
19         pass
20
21
22 class abstaining_renderer(renderer):
23     """A renderer that doesn't do it all the time."""
24
25     def __init__(self, name_to_timeout_dict: Dict[str, int]) -> None:
26         self.name_to_timeout_dict = name_to_timeout_dict
27         self.last_runs = {}
28         for key in name_to_timeout_dict:
29             self.last_runs[key] = 0.0
30
31     def should_render(self, keys_to_skip: Set[str]) -> Optional[str]:
32         now = time.time()
33         for key in self.name_to_timeout_dict:
34             if (
35                 (now - self.last_runs[key]) > self.name_to_timeout_dict[key]
36             ) and key not in keys_to_skip:
37                 return key
38         return None
39
40     @invocation_logged
41     def render(self) -> None:
42         tries_per_key: Dict[str, int] = {}
43         keys_to_skip: Set[str] = set()
44         while True:
45             key = self.should_render(keys_to_skip)
46             if key is None:
47                 break
48
49             if key in tries_per_key:
50                 tries_per_key[key] += 1
51             else:
52                 tries_per_key[key] = 0
53
54             if tries_per_key[key] >= 3:
55                 print(
56                     'renderer: Too many failures for "%s.%s", giving up'
57                     % (self.get_name(), key)
58                 )
59                 keys_to_skip.add(key)
60             else:
61                 msg = 'renderer: executing "%s.%s"' % (self.get_name(), key)
62                 if tries_per_key[key] > 1:
63                     msg = msg + " (retry #%d)" % tries_per_key[key]
64                 print(msg)
65                 if self.periodic_render(key):
66                     self.last_runs[key] = time.time()
67
68     @invocation_logged
69     @abstractmethod
70     def periodic_render(self, key) -> bool:
71         pass
72
73     def get_name(self) -> str:
74         return self.__class__.__name__
75
76
77 class debuggable_abstaining_renderer(abstaining_renderer):
78     def __init__(self, name_to_timeout_dict: Dict[str, int], debug: bool) -> None:
79         super(debuggable_abstaining_renderer, self).__init__(name_to_timeout_dict)
80         self.debug = debug
81
82     def debug_prefix(self) -> str:
83         return self.get_name()
84
85     def being_debugged(self) -> bool:
86         return self.debug
87
88     def debug_print(self, template: str, *args) -> None:
89         try:
90             if self.being_debugged():
91                 if args:
92                     msg = template.format(args)
93                 else:
94                     msg = template
95
96                 # current date and time
97                 now = datetime.now()
98                 timestamp = now.strftime("%d-%b-%Y (%H:%M:%S.%f)")
99                 print("%s(%s): %s" % (self.debug_prefix(), timestamp, msg))
100         except Exception as e:
101             print("Exception in debug_print!")
102             print(e)