#! /usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (C) 2011 Sebastian Pipping # Licensed under GPL v3 or later from __future__ import print_function import freitagslib.network as net from freitagslib.commands import BuyCommand, DepositCommand, ToGoCommand import colorama from colorama import Fore, Style import sys from decimal import Decimal import os import time import urllib2 COLOR_WARN = Fore.YELLOW COLOR_ERROR = Fore.RED COLOR_DEPOSIT = Fore.GREEN + Style.BRIGHT COLOR_TO_GO = Fore.MAGENTA + Style.BRIGHT COLOR_WITHDRAW = Fore.RED + Style.BRIGHT COLOR_SOME = Fore.WHITE + Style.BRIGHT COLOR_MUCH = Fore.YELLOW + Style.BRIGHT COLOR_RESET = Style.RESET_ALL def clear(): os.system('clear') TO_GO_ALL = 1 TO_GO_NONE = 0 TO_GO_PREV = -1 CODES = { 'UNDO':('undo',), 'COMMIT':('commit',), 'TO GO ALL':('to_go', TO_GO_ALL), 'TO GO NONE':('to_go', TO_GO_NONE), 'TO GO PREV':('to_go', TO_GO_PREV), 'DEPOSIT 0.01':('deposit', Decimal('0.01')), 'DEPOSIT 0.05':('deposit', Decimal('0.05')), 'DEPOSIT 0.10':('deposit', Decimal('0.10')), 'DEPOSIT 0.50':('deposit', Decimal('0.50')), 'DEPOSIT 1.00':('deposit', Decimal('1.00')), 'DEPOSIT 5.00':('deposit', Decimal('5.00')), 'DEPOSIT 10.00':('deposit', Decimal('10.00')), 'DEPOSIT 50.00':('deposit', Decimal('50.00')), } def delay(what, seconds): for i in xrange(seconds, 0, -1): if i < seconds: sys.stdout.write('\r') sys.stdout.write('%s in %d Sekunden... ' % (what, i)) sys.stdout.flush() time.sleep(1) def warn_balance(): print('Kontostrand im Minus, bitte Geld aufladen.') def error_page(message): clear() print(COLOR_ERROR + message + COLOR_RESET) print() delay('Weiter', 3) class Status: def __init__(self): self._reset() self.item_cache = dict() def _reset(self): self.auth_blob = None self.login_name = None self.balance = None self.transfers = None def dump(self): def sign(amount, plus='+'): return '-' if amount < 0 else plus def color(amount): return COLOR_WITHDRAW if amount < 0 else COLOR_DEPOSIT def color_command(command): diff = command.difference() if diff >= 0: return COLOR_DEPOSIT return COLOR_TO_GO if isinstance(command, ToGoCommand) else COLOR_WITHDRAW def show_total(balance, plus=' '): print('%3s %-40s %s%c %6.2f Euro%s' \ % ('', '', color(balance), sign(balance, plus), abs(balance), COLOR_RESET)) def show_item(position, command): diff = command.difference() label = command.label() print('%2d) %-40s %s%c %6.2f Euro%s' \ % (position, label, color_command(command), sign(diff), abs(diff), COLOR_RESET)) def show_bar(): print('%3s %-40s %13s' % ('', '', '=============')) if self.logged_in(): print('Eingeloggt als: %s%s%s' % (COLOR_SOME, self.login_name, COLOR_RESET)) print() if self.transfers: initial_command, initial_balance = self.transfers[0] print('Geplante Änderungen:') show_total(initial_balance) for i, (command, balance_backup) in enumerate(self.transfers): show_item(i + 1, command) show_bar() if len(self.transfers) > 1: show_total(self.balance - initial_balance, plus='+') show_bar() show_total(self.balance) if self.balance < 0: warn_balance() print() print(COLOR_SOME + 'Committen nicht vergessen.' + COLOR_RESET) else: print('Kontostand beträgt: %s%.2f Euro%s' % (COLOR_MUCH, self.balance, COLOR_RESET)) if self.balance < 0: print() warn_balance() else: print(COLOR_MUCH + 'Bitte einloggen.' + COLOR_RESET) print() print('Scanne dazu deine ID-Karte mit dem Barcode-Leser.') print() def logged_in(self): return self.auth_blob is not None def login(self, auth_blob): assert(not self.logged_in()) user_name = net.get_user_name_from_auth_blob(auth_blob) balance = net.get_balance(user_name) self.auth_blob = auth_blob self.login_name = user_name self.balance = balance self.transfers = list() def commit(self): assert(self.logged_in()) # Process command queue for (command, balance_backup) in list(self.transfers): try: command.run(self.login_name) except urllib2.HTTPError as e: error_page('FEHLER bei Kommunikation mit Server "%s"' % str(e)) break else: self.transfers.pop(0) if not self.transfers: # Show final balance for some time clear() self.dump() delay('Logout', 3) # Logout self._reset() def find(self, barcode): try: return self.item_cache[barcode] except KeyError: item = net.get_item(barcode) self.item_cache[barcode] = item return item def buy(self, item): assert(self.logged_in()) log_entry = (BuyCommand(item), self.balance) self.transfers.append(log_entry) self.balance = self.balance - item.price def deposit(self, amount): assert(self.logged_in()) log_entry = (DepositCommand(amount), self.balance) self.transfers.append(log_entry) self.balance = self.balance + amount def to_go(self, scope): _PRODUCT_FIRST = 'FEHLER: Bitte zuerst den betreffenden Artikel scannen.' if not self.transfers: error_page(_PRODUCT_FIRST) return if scope == TO_GO_ALL: after = list() dummy, initial_balance = self.transfers[0] balance_before = initial_balance def body(prev, command, balance_before): if isinstance(prev, BuyCommand) \ and prev.deposit() > 0 \ and not isinstance(command, ToGoCommand): to_go = ToGoCommand(prev) prev.set_to_go(True) after.append((to_go, balance_before)) balance_before += to_go.difference() if command is not None: after.append((command, balance_before)) balance_before += command.difference() return balance_before prev = None for command, dummy in list(self.transfers): balance_before = body(prev, command, balance_before) prev = command balance_before = body(prev, None, balance_before) self.balance = balance_before self.transfers = after elif scope == TO_GO_NONE: after = list() first_command, initial_balance = self.transfers[0] balance_before = initial_balance for command, dummy in list(self.transfers): if isinstance(command, BuyCommand) \ and command.is_to_go(): command.set_to_go(False) elif isinstance(command, ToGoCommand): continue after.append((command, balance_before)) balance_before += command.difference() self.balance = balance_before self.transfers = after elif scope == TO_GO_PREV: prev, balance_backup = self.transfers[-1] if not isinstance(prev, BuyCommand): error_page(_PRODUCT_FIRST) return to_go = ToGoCommand(prev) prev.set_to_go(True) self.transfers.append((to_go, self.balance)) self.balance += to_go.difference() def undo(self): assert(self.logged_in()) if self.transfers: (last_command, balance_backup) = self.transfers[-1] if isinstance(last_command, ToGoCommand) \ and self.transfers: second_last_command = self.transfers[-2] if isinstance(second_last_command, BuyCommand): second_last_command.set_to_go(False) self.transfers.pop() self.balance = balance_backup else: error_page('FEHLER: Nichts da, was ich rückgängig machen könnte.') def print_prompt(): sys.stdout.write(">>> ") def handle(line, status): if line == 'exit': clear() sys.exit(0) if status.logged_in(): if line in CODES: call = CODES[line] method = call[0] params = call[1:] getattr(status, method)(*params) else: try: item = status.find(line) except urllib2.HTTPError as e: if e.code == 404: # URL not found == item not found with REST error_page('FEHLER: Aktion oder Ware "%s" nicht bekannt' % line) else: error_page('FEHLER bei Kommunikation mit Server "%s"' % str(e)) else: status.buy(item) else: try: status.login(line) except urllib2.URLError as e: error_page('FEHLER bei Kommunikation mit Server "%s"' % str(e)) def main(): colorama.init() status = Status() while True: clear() status.dump() print_prompt() l = sys.stdin.readline() if not l: break line = l.rstrip() handle(line, status) if __name__ == '__main__': try: main() except KeyboardInterrupt: pass