9bf98e303a91d95c7aba32272f0351da4e7c1e0d
[kiosk.git] / chooser.py
1 import datetime
2 import os
3 import random
4 import re
5 import sys
6 import time
7 import glob
8 import constants
9 import trigger
10
11 class chooser(object):
12     """Base class of a thing that chooses pages"""
13     def get_page_list(self):
14         now = time.time()
15         valid_filename = re.compile("([^_]+)_(\d+)_([^\.]+)\.html")
16         filenames = []
17         pages = [ f for f in os.listdir(constants.pages_dir)
18                   if os.path.isfile(os.path.join(constants.pages_dir, f))]
19         for page in pages:
20             result = re.match(valid_filename, page)
21             if result != None:
22                 print(('chooser: candidate page: "%s"' % page))
23                 if (result.group(3) != "none"):
24                     freshness_requirement = int(result.group(3))
25                     last_modified = int(os.path.getmtime(
26                         os.path.join(constants.pages_dir, page)))
27                     age = (now - last_modified)
28                     if (age > freshness_requirement):
29                         print(('chooser: "%s" is too old.' % page))
30                         continue
31                 filenames.append(page)
32         return filenames
33
34     def choose_next_page(self):
35         pass
36
37 class weighted_random_chooser(chooser):
38     """Chooser that does it via weighted RNG."""
39     def dont_choose_page_twice_in_a_row_filter(self, choice):
40         if choice == self.last_choice:
41             return False
42         self.last_choice = choice
43         return True
44
45     def __init__(self, filter_list):
46         self.last_choice = ""
47         self.valid_filename = re.compile("([^_]+)_(\d+)_([^\.]+)\.html")
48         self.pages = None
49         self.count = 0
50         self.filter_list = filter_list
51         if filter_list is None:
52             self.filter_list = []
53         self.filter_list.append(self.dont_choose_page_twice_in_a_row_filter)
54
55     def choose_next_page(self):
56         if (self.pages == None or
57             self.count % 100 == 0):
58             self.pages = self.get_page_list()
59
60         total_weight = 0
61         weights = []
62         for page in self.pages:
63             result = re.match(self.valid_filename, page)
64             if result != None:
65                 weight = int(result.group(2))
66                 weights.append(weight)
67                 total_weight += weight
68         if (total_weight <= 0):
69             raise error
70
71         while True:
72             random_pick = random.randrange(0, total_weight - 1)
73             so_far = 0
74             for x in range(0, len(weights)):
75                 so_far += weights[x]
76                 if so_far > random_pick:
77                     break
78             choice = self.pages[x]
79
80             # Allow filter list to suppress pages.
81             choice_is_filtered = False
82             for f in self.filter_list:
83                 if not f(choice):
84                     print("chooser: %s filtered by %s" % (choice, f.__name__))
85                     choice_is_filtered = True
86                     break
87             if choice_is_filtered:
88                 continue
89
90             # We're good...
91             self.count += 1
92             return choice
93
94 class weighted_random_chooser_with_triggers(weighted_random_chooser):
95     """Same as WRC but has trigger events"""
96     def __init__(self, trigger_list, filter_list):
97         weighted_random_chooser.__init__(self, filter_list)
98         self.trigger_list = trigger_list
99         if trigger_list is None:
100             self.trigger_list = []
101         self.page_queue = set(())
102
103     def check_for_triggers(self):
104         triggered = False
105         for t in self.trigger_list:
106             x = t.get_triggered_page_list()
107             if x != None and len(x) > 0:
108                 for y in x:
109                     self.page_queue.add(y)
110                     triggered = True
111         return triggered
112
113     def choose_next_page(self):
114         if (self.pages == None or
115             self.count % 100 == 0):
116             self.pages = self.get_page_list()
117
118         triggered = self.check_for_triggers()
119
120         # First try to satisfy from the page queue.
121         if (len(self.page_queue) > 0):
122             print("chooser: Pulling page from queue...")
123             page = None
124             priority = None
125             for t in self.page_queue:
126                 if priority == None or t[1] > priority:
127                     page = t[0]
128                     priority = t[1]
129             self.page_queue.remove((page, priority))
130             return page, triggered
131
132         # Fall back on weighted random choice.
133         else:
134             return weighted_random_chooser.choose_next_page(self), False
135
136 class rotating_chooser(chooser):
137     """Chooser that does it in a rotation"""
138     def __init__(self):
139         self.valid_filename = re.compile("([^_]+)_(\d+)_([^\.]+)\.html")
140         self.pages = None
141         self.current = 0
142         self.count = 0
143
144     def choose_next_page(self):
145         if (self.pages == None or
146             self.count % 100 == 0):
147             self.pages = self.get_page_list()
148
149         if len(self.pages) == 0:
150             raise error
151
152         if (self.current >= len(self.pages)):
153             self.current = 0
154
155         page = self.pages[self.current]
156         self.current += 1
157         self.count += 1
158         return page
159
160 # Test
161 def filter_news_during_dinnertime(page):
162     now = datetime.datetime.now()
163     is_dinnertime = now.hour >= 17 and now.hour <= 20
164     return (not is_dinnertime or
165             not ("cnn" in page or
166                  "news" in page or
167                  "mynorthwest" in page or
168                  "seattle" in page or
169                  "stranger" in page or
170                  "twitter" in page or
171                  "wsj" in page))
172
173 #x = weighted_random_chooser_with_triggers([], [ filter_news_during_dinnertime ])
174 #print(x.choose_next_page())