#! /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 import colorama from colorama import Fore, Style import sys from decimal import Decimal import os import time import urllib2 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_ONLY = 2 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), 'TO GO ONLY':('to_go', TO_GO_ONLY), '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('Kontostand 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 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, diff, label, color): print('%2d) %-40s %s%c %6.2f Euro%s' \ % (position, label, color, 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) i = 1 for command, dummy in self.transfers: if isinstance(command, BuyCommand): if (command.includes_commodity()): show_item(i, -command.commodity_value(), command.commodity_label(), COLOR_WITHDRAW) i += 1 if (command.includes_deposit()): show_item(i, -command.deposit_value(), command.deposit_label(), COLOR_TO_GO) i += 1 else: show_item(i, command.difference(), command.label(), COLOR_DEPOSIT) i += 1 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: """ Makes all BuyCommands with deposit > 0 include deposit ...and updates future balance accordingly. """ dummy, initial_balance = self.transfers[0] balance_before = initial_balance for command, dummy in self.transfers: if isinstance(command, BuyCommand) \ and not command.includes_deposit() \ and command.deposit_value() > 0: command.include_deposit(True) balance_before += command.difference() self.balance = balance_before elif scope == TO_GO_NONE: """ Makes all BuyCommands that include commodity not include deposit any more ...and updates future balance accordingly. """ first_command, initial_balance = self.transfers[0] balance_before = initial_balance for command, dummy in self.transfers: if isinstance(command, BuyCommand) \ and command.includes_commodity(): command.include_deposit(False) balance_before += command.difference() self.balance = balance_before elif scope == TO_GO_PREV: """ Makes the last BuyCommand include deposit ...and updates future balance accordingly. """ prev, balance_backup = self.transfers[-1] if not isinstance(prev, BuyCommand): error_page(_PRODUCT_FIRST) return if prev.includes_deposit(): error_page('FEHLER: Pfand für Produkt "%s" bereits aktiviert' % prev.item_name()) return if prev.deposit_value() <= 0: error_page('FEHLER: Produkt "%s" hat kein Pfand' % prev.item_name()) return before = prev.difference() prev.include_deposit(True) after = prev.difference() self.balance += (after - before) elif scope == TO_GO_ONLY: """ Makes all BuyCommand that include commodity be deposit only ...and updates future balance accordingly. """ dummy, initial_balance = self.transfers[0] balance_before = initial_balance for command, dummy in self.transfers: if isinstance(command, BuyCommand) \ and command.deposit_value() > 0 \ and command.includes_commodity(): command.include_commodity(False) command.include_deposit(True) balance_before += command.difference() self.balance = balance_before def undo(self): assert(self.logged_in()) if self.transfers: dummy, balance_backup = self.transfers[-1] 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.HTTPError as e: if e.code == 404: # URL not found == user unknown error_page('FEHLER: Benutzer "%s" nicht bekannt' % line) else: error_page('FEHLER bei Kommunikation mit Server "%s"' % str(e)) 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() if line: handle(line, status) if __name__ == '__main__': try: main() except KeyboardInterrupt: pass