3 from datetime import datetime
6 from threading import Thread
9 from typing import Optional
14 import renderer_catalog
17 import trigger_catalog
21 def filter_news_during_dinnertime(page: str) -> bool:
23 is_dinnertime = now.hour >= 17 and now.hour <= 20
24 return not is_dinnertime or not (
27 or "mynorthwest" in page
35 def thread_change_current() -> None:
36 page_chooser = chooser.weighted_random_chooser_with_triggers(
37 trigger_catalog.get_triggers(), [filter_news_during_dinnertime]
39 swap_page_target = 0.0
43 (page, triggered) = page_chooser.choose_next_page()
46 print("chooser[%s] - WE ARE TRIGGERED." % utils.timestamp())
49 "chooser[%s] - EMERGENCY PAGE %s LOAD NEEDED"
50 % (utils.timestamp(), page)
53 f = open(os.path.join(constants.pages_dir, "current.shtml"), "w")
57 print("chooser[%s] - page does not exist?!" % (utils.timestamp()))
60 swap_page_target = now + constants.refresh_period_sec
62 # Also notify XMLHTTP clients that they need to refresh now.
63 path = os.path.join(constants.pages_dir, "reload_immediately.html")
65 f.write("Reload, suckers!")
68 # Fix this hack... maybe read the webserver logs and see if it
69 # actually was picked up?
73 elif now >= swap_page_target:
77 "chooser[%s] - nominal choice got the same page..."
82 print("chooser[%s] - nominal choice of %s" % (utils.timestamp(), page))
84 f = open(os.path.join(constants.pages_dir, "current.shtml"), "w")
88 print("chooser[%s] - page does not exist?!" % (utils.timestamp()))
91 swap_page_target = now + constants.refresh_period_sec
95 def emit_wrapped(f, filename) -> None:
96 def pick_background_color() -> str:
98 if now.hour <= 6 or now.hour >= 21:
100 elif now.hour == 7 or now.hour == 20:
105 age = utils.describe_age_of_file_briefly(f"pages/{filename}")
106 bgcolor = pick_background_color()
110 <TITLE>Kitchen Kiosk</TITLE>
111 <LINK rel="stylesheet" type="text/css" href="style.css">
112 <SCRIPT TYPE="text/javascript">
114 // Zoom the 'contents' div to fit without scrollbars and then make
116 function zoomScreen() {
119 document.getElementById("content").style.zoom = z+"%%";
120 var body = document.body;
121 var html = document.documentElement;
122 var height = Math.max(body.scrollHeight,
127 var windowHeight = window.innerHeight;
128 var width = Math.max(body.scrollWidth,
133 var windowWidth = window.innerWidth;
134 var heightRatio = height / windowHeight;
135 var widthRatio = width / windowWidth;
137 if (heightRatio <= 1.0 && widthRatio <= 1.0) {
142 document.getElementById("content").style.visibility = "visible";
145 // Load IMG tags with DATA-SRC attributes late.
146 function lateLoadImages() {
147 var image = document.getElementsByTagName('img');
148 for (var i = 0; i < image.length; i++) {
149 if (image[i].getAttribute('DATA-SRC')) {
150 image[i].setAttribute('SRC', image[i].getAttribute('DATA-SRC'));
155 // Operate the clock at the top of the page.
156 function runClock() {
157 var today = new Date();
158 var h = today.getHours();
159 var ampm = h >= 12 ? 'pm' : 'am';
161 h = h ? h : 12; // the hour '0' should be '12'
162 var m = maybeAddZero(today.getMinutes());
164 if (today.getSeconds() %% 2 == 0) {
165 colon = "<FONT STYLE='color: #%s; font-size: 4vmin; font-weight: bold'>:</FONT>";
167 document.getElementById("time").innerHTML = h + colon + m + ampm;
168 document.getElementById("date").innerHTML = today.toDateString();
169 var t = setTimeout(function(){runClock()}, 1000);
172 // Helper method for running the clock.
173 function maybeAddZero(x) {
174 return (x < 10) ? "0" + x : x;
177 // Do something on page load.
178 function addLoadEvent(func) {
179 var oldonload = window.onload;
180 if (typeof window.onload != 'function') {
181 window.onload = func;
183 window.onload = function() {
192 // Sleep thread helper.
193 const sleep = (milliseconds) => {
194 return new Promise(resolve => setTimeout(resolve, milliseconds))
197 var loadedDate = new Date();
199 addLoadEvent(zoomScreen);
200 addLoadEvent(runClock);
201 addLoadEvent(lateLoadImages);
203 // Runs the countdown line at the bottom and is responsible for
204 // normal page reloads caused by the expiration of a timer.
205 (function countdown() {
208 var now = new Date();
209 var deltaMs = now.getTime() - loadedDate.getTime();
211 var remainingMs = (totalMs - deltaMs);
213 if (remainingMs > 0) {
214 var hr = document.getElementById("countdown");
215 var width = (remainingMs / (totalMs - 5000)) * 100.0;
217 hr.style.visibility = "visible";
218 hr.style.width = " ".concat(width, "%%");
219 hr.style.backgroundColor = "maroon";
222 // Reload unconditionally after 22 sec.
223 window.location.reload();
226 // Brief sleep before doing it all over again.
227 sleep(50).then(() => {
233 // Periodically checks for emergency reload events.
237 var xhr = new XMLHttpRequest();
239 'http://wannabe.house/kiosk/pages/reload_immediately.html');
242 if (xhr.status === 200) {
243 window.location.reload();
245 sleep(500).then(() => {
256 <TABLE style="height:100%%; width:100%%" BORDER=0>
259 <DIV id="date"> </DIV>
261 <TD ALIGN="center"><FONT COLOR=#bbbbbb>
262 <DIV id="info"></DIV></FONT>
265 <DIV id="time"> </DIV>
268 <TR STYLE="vertical-align:top">
270 <DIV ID="content" STYLE="zoom: 1; visibility: hidden;">
271 <!-- BEGIN main page contents. -->
272 <!--#include virtual=\"%s\"-->
273 <!-- END main page contents. -->
276 <DIV STYLE="position: absolute; top:1030px; width:99%%">
278 <FONT SIZE=2 COLOR=#bbbbbb>%s @ %s ago.</FONT>
280 <HR id="countdown" STYLE="width:0px;
287 background-color:#ffffff;">
295 constants.refresh_period_sec * 1000,
304 def thread_invoke_renderers() -> None:
306 print(f"renderer[{utils.timestamp()}]: invoking all renderers in catalog...")
307 for r in renderer_catalog.get_renderers():
311 except Exception as e:
312 traceback.print_exc()
314 f"renderer[{utils.timestamp()}] unknown exception in {r.get_name()}, swallowing it."
316 delta = time.time() - now
319 f"renderer[{utils.timestamp()}]: Warning: {r.get_name()}'s rendering took {delta:%5.2f}s."
322 f"renderer[{utils.timestamp()}]: thread having a little break for {constants.render_period_sec}s..."
324 time.sleep(constants.render_period_sec)
327 if __name__ == "__main__":
328 logging.basicConfig()
329 changer_thread: Optional[Thread] = None
330 renderer_thread: Optional[Thread] = None
332 if changer_thread is None or not changer_thread.is_alive():
334 f"MAIN[{utils.timestamp()}] - (Re?)initializing chooser thread... (wtf?!)"
336 changer_thread = Thread(target=thread_change_current, args=())
337 changer_thread.start()
338 if renderer_thread is None or not renderer_thread.is_alive():
340 f"MAIN[{utils.timestamp()}] - (Re?)initializing render thread... (wtf?!)"
342 renderer_thread = Thread(target=thread_invoke_renderers, args=())
343 renderer_thread.start()