#!/usr/bin/env python3
# lunchuman.wsgi: a WSGI script for managing a simple lunch club
# Copyright (C) 2012 Niels G. W. Serup
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License along with this program. If not, see
# .
## Version: 0.1.0
## Maintainer: Niels G. W. Serup
## Temporary website: http://datalosjen.metanohi.name/static/source/lunchuman
import sys
import os
import bottle
from bottle import route, abort, error, request, redirect
import sqlite3
import datetime
import threading
import itertools
import atexit
import pickle
import configparser
import locale
import traceback
_preferred_encoding = 'utf-8' # locale.getpreferredencoding() # fail
_absfile = os.path.abspath(__file__)
_filedir = os.path.dirname(_absfile)
_configfile_name = 'lunchuman.config'
def start_bottle():
global application
application = bottle.default_app()
def block():
threading.Event().wait()
with open(os.path.join(_filedir, '.dynamicsettings'), 'rb') as f:
dynamic_settings = pickle.load(f)
if dynamic_settings['status'] == 'stop':
start_bottle()
block()
class LunchumanDatabase:
def __init__(self, fname):
self.dbfile = fname
def load(self):
self.conn = sqlite3.connect(self.dbfile)
self.cursor = self.conn.cursor()
self._create()
def close(self):
self.conn.close()
def _create(self):
self.cursor.execute('''
create table if not exists users (
id integer primary key autoincrement,
name text,
moneys int,
foods int
)
''')
self.cursor.execute('''
create table if not exists actions (
id integer primary key autoincrement,
action_when datetime,
action text
)
''')
self.conn.commit()
def get_users(self):
self.cursor.execute('select * from users order by moneys / (foods * 1.0) desc')
return self.cursor.fetchall()
def add_user(self, name):
self.cursor.execute('insert into users values (NULL, ?, 0, 0)', (name,))
self.log('new user created: {}'.format(repr(name)))
self.conn.commit()
def log(self, msg):
self.cursor.execute('insert into actions values (NULL, ?, ?)',
(datetime.datetime.utcnow(), msg))
self.conn.commit()
def get_log(self):
self.cursor.execute('select * from actions order by action_when desc')
return self.cursor.fetchall()
def update_user_data(self, *users):
log_msg = []
for id, new_name, moneys_incr, foods_incr in users:
try:
foods_incr = int(foods_incr)
except ValueError:
foods_incr = 0
try:
moneys_incr = int(moneys_incr)
except ValueError:
moneys_incr = 0
self.cursor.execute('select name from users where id=?', (id,))
old_name = self.cursor.fetchone()[0]
self.cursor.execute('''
update users set name=?, moneys=moneys + (?), foods=foods + (?)
where id=?
''', (new_name, moneys_incr, foods_incr, id))
loclog = []
if old_name != new_name:
loclog.append('change name to {}'.format(new_name))
if foods_incr > 0:
loclog.append('eat {} foods'.format(foods_incr))
elif foods_incr < 0:
loclog.append('uneat {} foods'.format(-foods_incr))
if moneys_incr > 0:
loclog.append('spend {} moneys'.format(moneys_incr))
elif moneys_incr < 0:
loclog.append('unspend {} moneys'.format(-moneys_incr))
if loclog:
log_msg.append('{} do:\n'.format(old_name)
+ ' ' + '\n '.join(loclog))
if log_msg:
self.log('\n'.join(log_msg))
self.conn.commit()
def add_settings_from_file(fname, settings):
cfg = configparser.SafeConfigParser(settings)
cfg.add_section('lunchuman')
cfg.read(os.path.join(_filedir, fname))
return {name: value for name, value in cfg.items('lunchuman')}
def dictpair(*ds):
return {key: val for key, val
in itertools.chain(*(d.items() for d in ds))}
def read_file(locpath):
with open(os.path.join(_filedir, locpath), 'rb') as f:
return f.read().decode(_preferred_encoding)
def get_seq_input(d, base_key):
i = 0
xs = []
while True:
try:
xs.append(d['{}[{}]'.format(base_key, i)])
except KeyError:
break
i += 1
return xs
settings = add_settings_from_file(
_configfile_name, {
'name': 'Lunchuman',
'dbfile': 'lunchuman.sqlite',
'template': 'template.html',
'frontpage': 'frontpage.html'
})
_template = read_file(settings['template'])
_userdata_template = '''
{moneys} +
{foods} +
{goodwill}
'''
_frontpage = _template.format(**dictpair(settings, {
'content': read_file(settings['frontpage']),
'headextra': ""
}))
db = LunchumanDatabase(os.path.join(_filedir, settings['dbfile']))
#atexit.register(db.close)
db.load()
@route('/')
def frontpage():
users = db.get_users()
userdata = '\n'.join(_userdata_template.format(
id=users[i][0], name=repr(users[i][1]), moneys=users[i][2],
foods=users[i][3], i=i, goodwill=('{:.2f}'.format(
users[i][2] / users[i][3]) if users[i][3] > 0
else None))
for i in range(len(users)))
log = '\n\n'.join('{} UTC:\n{}'.format(*row[1:]) for row in db.get_log())
return _frontpage.format(userdata=userdata, log=log)
@route('/adduser', method='post')
def add_user():
db.add_user(request.forms['name'])
redirect('/')
@route('/updateuserdata', method='post')
def update_user_data():
try:
users = (vals[1:] for vals in
filter(lambda vals: vals[0] != vals[2] or
(vals[3] != '0' and vals[3] != '') or
(vals[4] != '0' and vals[4] != ''),
zip(*(get_seq_input(request.forms, attr)
for attr in ('orig_name', 'id', 'new_name',
'moneys_incr', 'foods_incr')))))
db.update_user_data(*users)
except Exception:
traceback.print_exc()
redirect('/')
start_bottle()