def manual_start(self): """Start route processing in manual mode.""" self.manual_stop() cur_item = self.tree.focus() if cur_item in self.tree.get_children(): item = { 'route': self.routes[cur_item], 'route_id': cur_item, 'last_date': None } if self.oebb is None: item['oebb'] = OeBB() else: item['oebb'] = self.oebb self.oebb = None self.stop_thread = Event() item['exit'] = self.stop_thread item['iter_limit'] = self.FULL_ROUTE thread = Thread(target=self.process_route, args=(item, ), daemon=True) thread.start() self.current_route = item['route_id'] self.request_starter_event_id = None self.status.set('Processing: ' + OeBB.station_name(item['route']['stations'][0]) + ' - ' + OeBB.station_name(item['route']['stations'][1]))
def event_loop(self): """Process events in queue.""" while not self.queue.empty(): ev_type, ev = self.queue.get() if ev_type == self.ACTION_NEW_ROUTE: if ev['date'][0] == self.RouteWindow.DATE_DAYS: date_interval = 'Next %s day(s)' % ev['date'][1].days else: date_interval = ev['date'][1][0].strftime('%d/%m/%Y') if ev['date'][1][0] != ev['date'][1][1]: date_interval += ev['date'][1][1].strftime( ' - %d/%m/%Y') if ev['time'][0] == datetime.time.min and ev['time'][ 1] == datetime.time.max: time_interval = '' else: time_interval = ev['time'][0].strftime( '%H') + ' - ' + ev['time'][1].strftime('%H') route_id = self.tree.insert( '', 'end', values=[ OeBB.station_name(ev['stations'][0]), OeBB.station_name(ev['stations'][1]), date_interval, time_interval ]) self.routes[route_id] = ev self.best_connections[route_id] = [] self.request_queue.put( QueueItem(datetime.datetime.now(), { 'route': ev, 'route_id': route_id, 'last_date': None })) elif ev_type == self.ACTION_NEW_CONNECTION: if 'reducedScope' not in ev['price'] and ev[ 'route_id'] in self.routes: self.insert_connection(ev) elif ev_type == self.ACTION_FINISHED: if self.oebb is None: self.oebb = ev if self.manual.get(): if self.current_route is None: self.status.set('Select task') elif self.request_starter_event_id is None and ( self.stop_thread is None or self.stop_thread.is_set()): self.current_route = None self.request_starter_event_id = self.master.after( 30000, self.request_starter) self.status.set('Cooldown') self.master.after(200, self.event_loop)
def get_station(self): cur = self.current() if cur == -1: return None if self.get() != OeBB.station_name(self.stations[cur]): return None return self.stations[cur]
def request_starter(self): """Check if next route should be processed. Check if time has come to get connections of the next route in queue and spawn new thread. """ while not self.request_queue.empty(): item = self.request_queue.get() if item.data['route_id'] in self.routes: break else: self.request_starter_event_id = self.master.after( 1000, self.request_starter) self.status.set('Task queue is empty') return if item.priority > datetime.datetime.now(): self.request_queue.put(item) self.request_starter_event_id = self.master.after( 1000, self.request_starter) self.status.set('Next check at ' + item.priority.strftime('%H:%M')) return if self.oebb is None: item.data['oebb'] = OeBB() else: item.data['oebb'] = self.oebb self.oebb = None self.stop_thread = Event() item.data['exit'] = self.stop_thread item.data['iter_limit'] = 20 thread = Thread(target=self.process_route, args=(item.data, ), daemon=True) thread.start() self.current_route = item.data['route_id'] self.request_starter_event_id = None self.status.set('Processing: ' + OeBB.station_name(item.data['route']['stations'][0]) + ' - ' + OeBB.station_name(item.data['route']['stations'][1]))
def update_connections(self, route_id): """Update route connections in treeview.""" if not self.tree.exists(route_id): return items = self.tree.get_children(item=route_id) for item in items: self.tree.delete(item) for connection in self.best_connections[route_id][:5]: con_datetime = OeBB.get_datetime( connection['connection']['from']['departure']) self.tree.insert(route_id, 'end', values=[ '', '', con_datetime.strftime('%d/%m/%Y'), con_datetime.strftime('%H:%M'), connection['price']['price'] ])
def delete_old_connections(self): """Delete expired connections and update treeview.""" now = datetime.datetime.now() for route in self.routes: con_len = len(self.best_connections[route]) last_shown = self.best_connections[route][ 4] if con_len >= 5 else None self.best_connections[route][:] = [ con for con in self.best_connections[route] if OeBB.get_datetime(con['connection']['from']['departure']) > now ] con_len_new = len(self.best_connections[route]) if con_len != con_len_new: if con_len_new < 5: self.update_connections(route) elif last_shown != self.best_connections[route][4]: self.update_connections(route) self.master.after(60000, self.delete_old_connections)
def __init__(self, master, queue): super().__init__(master) self.title('Add route') self.protocol('WM_DELETE_WINDOW', self.destroy) self.queue = queue oebb = OeBB() main_frame = tk.Frame(self) main_frame.pack(fill=tk.BOTH) # ----- Origin and destination selectors ----- city_frame = tk.Frame(main_frame) city_frame.pack(fill=tk.X) city_frame_left = tk.Frame(city_frame) city_frame_left.pack(side=tk.LEFT, padx=5) tk.Label(city_frame_left, text='Origin:').pack(anchor=tk.W) city_origin = CitySelector(city_frame_left, oebb) city_origin.pack() city_frame_right = tk.Frame(city_frame) city_frame_right.pack(side=tk.LEFT, padx=5) tk.Label(city_frame_right, text='Destination:').pack(anchor=tk.W) city_destination = CitySelector(city_frame_right, oebb) city_destination.pack() # ----- Date selectors ----- self.date_opt = tk.IntVar() date_frame = tk.LabelFrame(main_frame, text='Date') date_frame.pack(anchor=tk.W, padx=5) date_frame_days = tk.Frame(date_frame) date_frame_days.pack(anchor=tk.W) tk.Radiobutton(date_frame_days, text='Days', variable=self.date_opt, value=self.DATE_DAYS)\ .pack(side=tk.LEFT) days = ttk.Combobox(date_frame_days, state='readonly', width=5) days.pack(side=tk.LEFT, padx=5) days['values'] = [str(x) for x in range(1, 31)] days.current(0) date_frame_fixed = tk.Frame(date_frame) date_frame_fixed.pack(anchor=tk.W) tk.Radiobutton(date_frame_fixed, text='Fixed', variable=self.date_opt, value=self.DATE_FIXED)\ .pack(side=tk.LEFT) cal_from = tkcalendar.DateEntry(date_frame_fixed, width=10, state='readonly') cal_from.pack(padx=3, pady=5, ipady=3, side=tk.LEFT) tk.Label(date_frame_fixed, text='-').pack(side=tk.LEFT) cal_to = tkcalendar.DateEntry(date_frame_fixed, width=10, state='readonly') cal_to.pack(padx=3, pady=5, ipady=3, side=tk.LEFT) # Make sure that date interval is always valid (start <= end) def date_from_selected(_): first = cal_from.get_date() second = cal_to.get_date() if second < first: cal_to.set_date(first) def date_to_selected(_): first = cal_from.get_date() second = cal_to.get_date() if second < first: cal_from.set_date(second) cal_from.bind('<<DateEntrySelected>>', date_from_selected) cal_to.bind('<<DateEntrySelected>>', date_to_selected) # ----- Time selectors ----- time_frame = tk.LabelFrame(main_frame, text='Time') time_frame.pack(anchor=tk.W, padx=5) time_from = ttk.Combobox(time_frame, width=6, state='readonly') time_from.pack(side=tk.LEFT, padx=3, pady=5) self.set_time_values(time_from, 0, 24) time_from.current(0) tk.Label(time_frame, text='-').pack(side=tk.LEFT) time_to = ttk.Combobox(time_frame, width=6, state='readonly') time_to.pack(side=tk.LEFT, padx=3, pady=5) self.set_time_values(time_to, 1, 25) time_to.current(len(time_to['values']) - 1) def time_from_selected(_): first = int(time_from.get()[:2]) second = int(time_to.get()[:2]) self.set_time_values(time_to, first + 1, 25) if second <= first: time_to.current(0) time_from.bind('<<ComboboxSelected>>', time_from_selected) # ----- Confirmation button ----- def validate(): city_from = city_origin.get_station() city_to = city_destination.get_station() if city_from and city_to: _time_to = int(time_to.get()[:2]) if _time_to == 24: _time_to = datetime.time.max else: _time_to = datetime.time(hour=_time_to) if self.date_opt.get() == self.DATE_DAYS: dates = datetime.timedelta(days=int(days.get())) else: dates = (cal_from.get_date(), cal_to.get_date()) self.queue.put((OeBBCheapTicketsFinder.ACTION_NEW_ROUTE, { 'stations': (city_from, city_to), 'date': (self.date_opt.get(), dates), 'time': (datetime.time(hour=int(time_from.get()[:2])), _time_to) })) self.destroy() ttk.Button(main_frame, text='Confirm', command=validate).pack(pady=5)
def process_route(self, item): """Retrieve connections that satisfies route restrictions.""" last_date = item['last_date'] last_req = 0 i = 0 while True: if item['exit'].is_set(): if item['iter_limit'] != self.FULL_ROUTE: item['last_date'] = last_date self.request_queue.put( QueueItem(datetime.datetime.now(), item)) self.queue.put((self.ACTION_FINISHED, item['oebb'])) return # Get connections sleep_time = 1 while True: next_date = self.next_valid_date(last_date, item['route']) try: if i == 0 or next_date != last_date or (time.time() - last_req) > 2300: connections = item['oebb'].connections( item['route']['stations'][0], item['route']['stations'][1], next_date) else: connections = item['oebb'].next_connections( connections[-1]) last_req = time.time() break except Exception as _: time.sleep(sleep_time) sleep_time = min(sleep_time + 1, 30) # Get prices sleep_time = 1 while True: try: prices = item['oebb'].prices(connections) break except: time.sleep(sleep_time) sleep_time = min(sleep_time + 1, 30) for index, connection in enumerate(connections): con_datetime = OeBB.get_datetime( connection['from']['departure']) if self.check_date(con_datetime, item['route']): self.queue.put((self.ACTION_NEW_CONNECTION, { 'connection': connection, 'price': prices[index], 'route_id': item['route_id'] })) if len(connections) == 0 or \ self.next_valid_date(OeBB.get_datetime(connections[-1]['from']['departure']), item['route']) is None: if item['iter_limit'] != self.FULL_ROUTE: item['last_date'] = None self.request_queue.put( QueueItem( datetime.datetime.now() + datetime.timedelta(minutes=30), item)) item['exit'].set() self.queue.put((self.ACTION_FINISHED, item['oebb'])) return last_date = OeBB.get_datetime(connections[-1]['from']['departure']) time.sleep(3) i += 1 if item['iter_limit'] != self.FULL_ROUTE and i == item[ 'iter_limit']: break if item['iter_limit'] != self.FULL_ROUTE: item['last_date'] = last_date self.request_queue.put( QueueItem( datetime.datetime.now() + datetime.timedelta(minutes=10), item)) item['exit'].set() self.queue.put((self.ACTION_FINISHED, item['oebb']))
def __init__(self, master=None): self.master = master self.queue = Queue() self.request_queue = PriorityQueue() self.routes = {} self.best_connections = {} self.oebb = OeBB() self.stop_thread = None self.current_route = None self.request_starter_event_id = None master.protocol('WM_DELETE_WINDOW', self.on_close) if os.path.exists('routes'): with open('routes', 'rb') as file: routes = pickle.load(file) for route in routes: self.queue.put((self.ACTION_NEW_ROUTE, routes[route])) main_frame = tk.Frame(master) main_frame.pack(fill=tk.BOTH, expand=1) # ----- Manual control ----- manual_frame = tk.Frame(main_frame) manual_frame.pack(fill=tk.X) # Mode selector self.manual = tk.BooleanVar() ttk.Checkbutton(manual_frame, text='Manual', variable=self.manual, command=self.change_mode).pack(side=tk.LEFT) self.but_frame = tk.Frame(manual_frame) self.but_frame.pack(side=tk.LEFT) # Manual control buttons ttk.Button(self.but_frame, text='Start', command=self.manual_start, state=tk.DISABLED) \ .pack(side=tk.LEFT, padx=5) ttk.Button(self.but_frame, text='Stop', command=self.manual_stop, state=tk.DISABLED) \ .pack(side=tk.LEFT) # ----- Main tree view ----- tree_columns = [('city_from', 'Origin', 100), ('city_to', 'Destination', 100), ('date_interval', 'Date', 100), ('time_interval', 'Time', 100), ('price', 'Price', 50)] self.tree = ttk.Treeview(main_frame, selectmode=tk.BROWSE, heigh=6, columns=list( map(lambda col: col[0], tree_columns))) for col in tree_columns: self.tree.heading(col[0], text=col[1]) self.tree.column(col[0], width=col[2]) self.tree.column('#0', width=10) self.tree.bind('<Delete>', self.delete_selected_route) self.tree.pack(fill=tk.BOTH, expand=1) # ----- "Add route" button ----- add_but = ttk.Button( main_frame, text='Add route', command=lambda: self.RouteWindow(master, self.queue)) add_but.pack() # ----- Status bar ----- self.status = tk.StringVar() tk.Label(main_frame, textvariable=self.status, bd=1, relief=tk.SUNKEN, anchor=tk.W).pack(fill=tk.X) self.status.set('Waiting for task') self.master.after(200, self.event_loop) self.request_starter_event_id = self.master.after( 1000, self.request_starter) self.master.after(60000, self.delete_old_connections)