Skip to content
Newer
Older
100755 776 lines (648 sloc) 16.5 KB
f175cf5 @esmil initial commit
esmil authored
1 #!/usr/bin/env lem
2
3 local utils = require 'lem.utils'
145242f @esmil update to latest LEM
esmil authored
4 local io = require 'lem.io'
5 local sha1 = require 'sha1'
f175cf5 @esmil initial commit
esmil authored
6 local sqlite = require 'lem.sqlite3'
7 local bqueue = require 'bqueue'
8
9 local assert, error = assert, error
10 local type, tostring = type, tostring
145242f @esmil update to latest LEM
esmil authored
11 local write, format = io.write, string.format
f175cf5 @esmil initial commit
esmil authored
12
145242f @esmil update to latest LEM
esmil authored
13 local db = assert(sqlite.open(arg[1] or 'test.db', sqlite.READWRITE))
f175cf5 @esmil initial commit
esmil authored
14 local timeout = 30
15
bce0d74 @esmil add some more comments
esmil authored
16 --- some helper functions ---
17
ea75e9d @esmil show user logs
esmil authored
18 function string.utf8trim(str, len)
19 local chars, i, n = 0, 1, #str
20 while i <= n do
21 local c = str:byte(i)
22 if c >= 252 then i = i + 5
23 elseif c >= 248 then i = i + 4
24 elseif c >= 240 then i = i + 3
25 elseif c >= 224 then i = i + 2
26 elseif c >= 192 then i = i + 1
27 end
28
29 chars = chars + 1
30 if chars == len then
31 return str:sub(1, i)
32 end
33
34 i = i + 1
35 end
36
37 return str .. (' '):rep(len - chars)
38 end
39
145242f @esmil update to latest LEM
esmil authored
40 local function print(...)
41 return write(format(...), '\n')
42 end
43 local function clearscreen()
44 return write "\x1B[1J\x1B[H"
45 end
07467f5 @esmil display menus and clear screen automatically
esmil authored
46
47 local function main_menu()
48 print "-------------------------------------------"
49 print " Swipe card to log in."
50 print " Scan barcode to check price of product."
51 print ""
79b00b2 @esmil allow multiple purchases
esmil authored
52 print " 1 | Create new account."
53 print " 2 | Update or create new product."
11365d3 @knielsen Allow to enter barcode explicitly with /<barcode>.
knielsen authored
54 print " /<p>| Check price for the product <p>."
4764fba @esmil support transfers
esmil authored
55 print " . | Print this menu."
07467f5 @esmil display menus and clear screen automatically
esmil authored
56 print "-------------------------------------------"
de8e5bd @esmil show some stats after the main menu
esmil authored
57
58 local r = assert(db:fetchone(
59 "SELECT SUM(balance)/COUNT(1), MIN(balance) FROM users"))
d5d4a73 Add display of total debts.
Unknown authored
60 local s = assert(db:fetchone(
61 "SELECT SUM(balance) FROM users WHERE balance < 0"))
de8e5bd @esmil show some stats after the main menu
esmil authored
62 print(" Average balance: %16.2f DKK", r[1])
63 print(" Largest single debt: %16.2f DKK", r[2])
d5d4a73 Add display of total debts.
Unknown authored
64 print(" Total debt: %16.2f DKK", s[1])
07467f5 @esmil display menus and clear screen automatically
esmil authored
65 end
66
67 local function user_menu()
68 print "-------------------------------------------"
69 print " Swipe card to switch user."
70 print " Scan barcode to buy product."
0f9849e @esmil press enter to log out
esmil authored
71 print " Press enter to log out."
07467f5 @esmil display menus and clear screen automatically
esmil authored
72 print ""
4764fba @esmil support transfers
esmil authored
73 print " / | Switch card."
74 print " * | Show my log."
75 print " + | Add money to account."
76 print " - | Transfer money."
79b00b2 @esmil allow multiple purchases
esmil authored
77 print " <n> | Buy <n> items."
11365d3 @knielsen Allow to enter barcode explicitly with /<barcode>.
knielsen authored
78 print " /<p>| Buy the product <p>."
4764fba @esmil support transfers
esmil authored
79 print " . | Print this menu."
07467f5 @esmil display menus and clear screen automatically
esmil authored
80 print "-------------------------------------------"
81 end
82
83 local function idle()
84 clearscreen()
85 main_menu()
86 return 'IDLE'
87 end
88
f175cf5 @esmil initial commit
esmil authored
89 local function login(hash, id)
90 local r = assert(db:fetchone("\z
640f547 @esmil update database schema
esmil authored
91 SELECT id, name, balance \z
92 FROM users \z
f175cf5 @esmil initial commit
esmil authored
93 WHERE hash = ?", hash))
94
95 if r == true then
96 if id then
97 clearscreen()
07467f5 @esmil display menus and clear screen automatically
esmil authored
98 print " Unknown card swiped, logged out."
99 main_menu()
f175cf5 @esmil initial commit
esmil authored
100 else
07467f5 @esmil display menus and clear screen automatically
esmil authored
101 print " Unknown card swiped.."
f175cf5 @esmil initial commit
esmil authored
102 end
626582e @esmil use all uppercase names for states
esmil authored
103 return 'MAIN'
f175cf5 @esmil initial commit
esmil authored
104 end
105
07467f5 @esmil display menus and clear screen automatically
esmil authored
106 clearscreen()
f175cf5 @esmil initial commit
esmil authored
107 print("-------------------------------------------")
07467f5 @esmil display menus and clear screen automatically
esmil authored
108 print(" Logged in as : %s", r[2])
109 print(" Balance : %.2f DKK", r[3])
110 print("")
111 print(" NB. If your name is just numbers,")
112 print(" please tell Esmil to change it.")
113 user_menu()
626582e @esmil use all uppercase names for states
esmil authored
114 return 'USER', r[1]
f175cf5 @esmil initial commit
esmil authored
115 end
116
b3feb17 @esmil implement editing products
esmil authored
117 local function product_dump(p)
118 print("-------------------------------------------")
07467f5 @esmil display menus and clear screen automatically
esmil authored
119 print(" Product : %s", p[1])
120 print(" Price : %.2f DKK", p[2])
b3feb17 @esmil implement editing products
esmil authored
121 print("-------------------------------------------")
122 end
123
f175cf5 @esmil initial commit
esmil authored
124 --- declare states ---
125
626582e @esmil use all uppercase names for states
esmil authored
126 MAIN = {
07467f5 @esmil display menus and clear screen automatically
esmil authored
127 wait = timeout,
128 timeout = idle,
129
f175cf5 @esmil initial commit
esmil authored
130 card = login,
131
132 barcode = function(code)
14bcf26 @esmil add switch card functionality
esmil authored
133 print " Price check.."
f175cf5 @esmil initial commit
esmil authored
134
b3feb17 @esmil implement editing products
esmil authored
135 local r = assert(db:fetchone(
136 "SELECT name, price FROM products WHERE barcode = ?", code))
f175cf5 @esmil initial commit
esmil authored
137 if r == true then
07467f5 @esmil display menus and clear screen automatically
esmil authored
138 print " Unknown product."
626582e @esmil use all uppercase names for states
esmil authored
139 return 'MAIN'
f175cf5 @esmil initial commit
esmil authored
140 end
141
b3feb17 @esmil implement editing products
esmil authored
142 product_dump(r)
626582e @esmil use all uppercase names for states
esmil authored
143 return 'MAIN'
f175cf5 @esmil initial commit
esmil authored
144 end,
145
146 keyboard = {
147 ['1'] = function()
07467f5 @esmil display menus and clear screen automatically
esmil authored
148 print " Please enter user name (or press enter to abort):"
d0a81db @esmil create new accounts with 0.00 DKK
esmil authored
149 return 'NEWUSER_NAME'
f175cf5 @esmil initial commit
esmil authored
150 end,
151 ['2'] = function()
11365d3 @knielsen Allow to enter barcode explicitly with /<barcode>.
knielsen authored
152 print(" Scan barcode (or /<barcode>, or press enter to abort):")
b3feb17 @esmil implement editing products
esmil authored
153 return 'PROD_CODE'
f175cf5 @esmil initial commit
esmil authored
154 end,
4764fba @esmil support transfers
esmil authored
155 ['.'] = function()
79b00b2 @esmil allow multiple purchases
esmil authored
156 main_menu()
157 return 'MAIN'
158 end,
f175cf5 @esmil initial commit
esmil authored
159 [''] = function()
07467f5 @esmil display menus and clear screen automatically
esmil authored
160 print(" ENTAR!")
626582e @esmil use all uppercase names for states
esmil authored
161 return 'MAIN'
f175cf5 @esmil initial commit
esmil authored
162 end,
163 function(cmd) --default
11365d3 @knielsen Allow to enter barcode explicitly with /<barcode>.
knielsen authored
164 if cmd:sub(1,1) == '/' then
165 return (MAIN['barcode'])(cmd:sub(2))
166 end
07467f5 @esmil display menus and clear screen automatically
esmil authored
167 print(" Unknown command '%s'.", cmd)
168 main_menu()
626582e @esmil use all uppercase names for states
esmil authored
169 return 'MAIN'
f175cf5 @esmil initial commit
esmil authored
170 end,
171 },
172 }
173
c29fe67 @esmil remove duplicated code
esmil authored
174 IDLE = {
175 card = MAIN.card,
176 barcode = MAIN.barcode,
177 keyboard = MAIN.keyboard,
178 }
179
626582e @esmil use all uppercase names for states
esmil authored
180 NEWUSER_NAME = {
b3feb17 @esmil implement editing products
esmil authored
181 wait = 120, -- allow 2 minutes for typing account name
f175cf5 @esmil initial commit
esmil authored
182 timeout = function()
07467f5 @esmil display menus and clear screen automatically
esmil authored
183 print " Aborted due to inactivity."
626582e @esmil use all uppercase names for states
esmil authored
184 return 'MAIN'
f175cf5 @esmil initial commit
esmil authored
185 end,
186
187 card = login,
188
626582e @esmil use all uppercase names for states
esmil authored
189 barcode = 'NEWUSER_NAME',
f175cf5 @esmil initial commit
esmil authored
190
191 keyboard = {
192 [''] = function()
07467f5 @esmil display menus and clear screen automatically
esmil authored
193 print " Aborted."
626582e @esmil use all uppercase names for states
esmil authored
194 return 'MAIN'
f175cf5 @esmil initial commit
esmil authored
195 end,
d0a81db @esmil create new accounts with 0.00 DKK
esmil authored
196 function(name) --default
07467f5 @esmil display menus and clear screen automatically
esmil authored
197 print(" Hello %s! Please swipe your card..", name)
d0a81db @esmil create new accounts with 0.00 DKK
esmil authored
198 return 'NEWUSER_HASH', name
f175cf5 @esmil initial commit
esmil authored
199 end,
200 },
201 }
202
d0a81db @esmil create new accounts with 0.00 DKK
esmil authored
203 NEWUSER_HASH = {
f175cf5 @esmil initial commit
esmil authored
204 wait = timeout,
205 timeout = function()
07467f5 @esmil display menus and clear screen automatically
esmil authored
206 print " Aborted due to inactivity."
626582e @esmil use all uppercase names for states
esmil authored
207 return 'MAIN'
f175cf5 @esmil initial commit
esmil authored
208 end,
209
d0a81db @esmil create new accounts with 0.00 DKK
esmil authored
210 card = function(hash, name)
07467f5 @esmil display menus and clear screen automatically
esmil authored
211 print " Card swiped, thank you! Creating account.."
f175cf5 @esmil initial commit
esmil authored
212
d0a81db @esmil create new accounts with 0.00 DKK
esmil authored
213 local ok, err = db:fetchone("\z
640f547 @esmil update database schema
esmil authored
214 INSERT INTO users (name, hash, balance) \z
215 VALUES (?, ?, 0.0)", name, hash)
f175cf5 @esmil initial commit
esmil authored
216
d0a81db @esmil create new accounts with 0.00 DKK
esmil authored
217 if not ok then
07467f5 @esmil display menus and clear screen automatically
esmil authored
218 print(" Error creating account: %s", err)
626582e @esmil use all uppercase names for states
esmil authored
219 return 'MAIN'
d0a81db @esmil create new accounts with 0.00 DKK
esmil authored
220 end
f175cf5 @esmil initial commit
esmil authored
221
d0a81db @esmil create new accounts with 0.00 DKK
esmil authored
222 return login(hash)
223 end,
f175cf5 @esmil initial commit
esmil authored
224
d0a81db @esmil create new accounts with 0.00 DKK
esmil authored
225 barcode = 'NEWUSER_HASH',
f175cf5 @esmil initial commit
esmil authored
226
d0a81db @esmil create new accounts with 0.00 DKK
esmil authored
227 keyboard = function()
07467f5 @esmil display menus and clear screen automatically
esmil authored
228 print " Aborted."
d0a81db @esmil create new accounts with 0.00 DKK
esmil authored
229 return 'MAIN'
230 end,
f175cf5 @esmil initial commit
esmil authored
231 }
232
b3feb17 @esmil implement editing products
esmil authored
233 PROD_CODE = {
f175cf5 @esmil initial commit
esmil authored
234 wait = timeout,
235 timeout = function()
07467f5 @esmil display menus and clear screen automatically
esmil authored
236 print " Aborted due to inactivity."
626582e @esmil use all uppercase names for states
esmil authored
237 return 'MAIN'
f175cf5 @esmil initial commit
esmil authored
238 end,
239
240 card = login,
241
242 barcode = function(code)
07467f5 @esmil display menus and clear screen automatically
esmil authored
243 print(" Scanned: %s", code)
b3feb17 @esmil implement editing products
esmil authored
244
245 local r = assert(db:fetchone("\z
246 SELECT id, name, price \z
247 FROM products \z
248 WHERE barcode = ?", code))
249
250 if r == true then
07467f5 @esmil display menus and clear screen automatically
esmil authored
251 print " Not found in database, creating new product."
252 print " Type name of product (or press enter to abort):"
b3feb17 @esmil implement editing products
esmil authored
253 return 'PROD_NEW_NAME', code
254 end
255
07467f5 @esmil display menus and clear screen automatically
esmil authored
256 print(" Already in database, updating info.")
257 print(" Type name of product (or press enter to keep '%s'):", r[2])
b3feb17 @esmil implement editing products
esmil authored
258 return 'PROD_EDIT_NAME', { id = r[1], name = r[2], price = r[3] }
f175cf5 @esmil initial commit
esmil authored
259 end,
260
11365d3 @knielsen Allow to enter barcode explicitly with /<barcode>.
knielsen authored
261 keyboard = function(entry)
262 if entry:sub(1,1) == '/' then
263 return (PROD_CODE['barcode'])(entry:sub(2))
264 end
07467f5 @esmil display menus and clear screen automatically
esmil authored
265 print " Aborted."
626582e @esmil use all uppercase names for states
esmil authored
266 return 'MAIN'
f175cf5 @esmil initial commit
esmil authored
267 end,
268 }
269
b3feb17 @esmil implement editing products
esmil authored
270 PROD_NEW_NAME = {
271 wait = 120, -- allow 2 minutes for typing product name
f175cf5 @esmil initial commit
esmil authored
272 timeout = function()
07467f5 @esmil display menus and clear screen automatically
esmil authored
273 print " Aborted due to inactivity."
626582e @esmil use all uppercase names for states
esmil authored
274 return 'MAIN'
f175cf5 @esmil initial commit
esmil authored
275 end,
276
277 card = login,
278
b3feb17 @esmil implement editing products
esmil authored
279 barcode = 'PROD_NEW_NAME',
f175cf5 @esmil initial commit
esmil authored
280
281 keyboard = {
282 [''] = function()
07467f5 @esmil display menus and clear screen automatically
esmil authored
283 print " Aborted."
626582e @esmil use all uppercase names for states
esmil authored
284 return 'MAIN'
f175cf5 @esmil initial commit
esmil authored
285 end,
286 function(name, code) --default
07467f5 @esmil display menus and clear screen automatically
esmil authored
287 print " Enter price (or press enter to abort):"
b3feb17 @esmil implement editing products
esmil authored
288 return 'PROD_NEW_PRICE', name, code
f175cf5 @esmil initial commit
esmil authored
289 end,
290 },
291 }
292
b3feb17 @esmil implement editing products
esmil authored
293 PROD_NEW_PRICE = {
f175cf5 @esmil initial commit
esmil authored
294 wait = timeout,
295 timeout = function()
07467f5 @esmil display menus and clear screen automatically
esmil authored
296 print " Aborted due to inactivity."
626582e @esmil use all uppercase names for states
esmil authored
297 return 'MAIN'
f175cf5 @esmil initial commit
esmil authored
298 end,
299
300 card = login,
301
b3feb17 @esmil implement editing products
esmil authored
302 barcode = 'PROD_NEW_PRICE',
f175cf5 @esmil initial commit
esmil authored
303
304 keyboard = {
305 [''] = function()
07467f5 @esmil display menus and clear screen automatically
esmil authored
306 print " Aborted."
626582e @esmil use all uppercase names for states
esmil authored
307 return 'MAIN'
f175cf5 @esmil initial commit
esmil authored
308 end,
309 function(price, name, code) --default
310 local n = tonumber(price)
311 if not n then
07467f5 @esmil display menus and clear screen automatically
esmil authored
312 print(" Unable to parse '%s', try again (or press enter to abort):", price)
b3feb17 @esmil implement editing products
esmil authored
313 return 'PROD_NEW_PRICE', name, code
f175cf5 @esmil initial commit
esmil authored
314 end
315
07467f5 @esmil display menus and clear screen automatically
esmil authored
316 print " Creating new product.."
f175cf5 @esmil initial commit
esmil authored
317
318 local ok, err = db:fetchone("\z
319 INSERT INTO products (barcode, price, name) \z
320 VALUES (?, ?, ?)", code, n, name)
321
2382f63 @esmil fix boolean error, thanks for reporting Asbjørn!
esmil authored
322 if not ok then
07467f5 @esmil display menus and clear screen automatically
esmil authored
323 print(" Error creating product: %s", err)
b3feb17 @esmil implement editing products
esmil authored
324 return 'MAIN'
f175cf5 @esmil initial commit
esmil authored
325 end
326
b3feb17 @esmil implement editing products
esmil authored
327 product_dump(assert(db:fetchone(
328 "SELECT name, price FROM products WHERE barcode = ?", code)))
626582e @esmil use all uppercase names for states
esmil authored
329 return 'MAIN'
f175cf5 @esmil initial commit
esmil authored
330 end,
331 },
332 }
333
b3feb17 @esmil implement editing products
esmil authored
334 PROD_EDIT_NAME = {
335 wait = 120, -- allow 2 minutes for typing product name
336 timeout = function()
07467f5 @esmil display menus and clear screen automatically
esmil authored
337 print " Aborted due to inactivity."
b3feb17 @esmil implement editing products
esmil authored
338 return 'MAIN'
339 end,
340
341 card = login,
342
343 barcode = 'PROD_EDIT_NAME',
344
b80e368 @esmil fix bug when updating price
esmil authored
345 keyboard = function(name, product)
346 if name ~= '' then
b3feb17 @esmil implement editing products
esmil authored
347 product.name = name
b80e368 @esmil fix bug when updating price
esmil authored
348 end
349
07467f5 @esmil display menus and clear screen automatically
esmil authored
350 print(" Type new price (or press enter to keep %.2f DKK):", product.price)
b80e368 @esmil fix bug when updating price
esmil authored
351 return 'PROD_EDIT_PRICE', product
352 end,
b3feb17 @esmil implement editing products
esmil authored
353 }
354
355 PROD_EDIT_PRICE = {
356 wait = timeout,
357 timeout = function()
07467f5 @esmil display menus and clear screen automatically
esmil authored
358 print " Aborted due to inactivity."
b3feb17 @esmil implement editing products
esmil authored
359 return 'MAIN'
360 end,
361
362 card = login,
363
364 barcode = 'PROD_EDIT_PRICE',
365
366 keyboard = function(price, product)
367 if price ~= '' then
368 local n = tonumber(price)
369 if not n then
07467f5 @esmil display menus and clear screen automatically
esmil authored
370 print(" Unable to parse '%s', try again (or press enter to keep %.2f DKK):",
b3feb17 @esmil implement editing products
esmil authored
371 price, product.price)
372 return 'PROD_EDIT_PRICE', product
373 end
d0823a4 @esmil remember to set the updated price, thanks Alvin!
esmil authored
374 product.price = n
b3feb17 @esmil implement editing products
esmil authored
375 end
376
07467f5 @esmil display menus and clear screen automatically
esmil authored
377 print " Updating product.."
b3feb17 @esmil implement editing products
esmil authored
378
379 local ok, err = db:fetchone("\z
380 UPDATE products \z
381 SET name = ?, price = ? \z
382 WHERE id = ?", product.name, product.price, product.id)
383
384 if not ok then
07467f5 @esmil display menus and clear screen automatically
esmil authored
385 print(" Error updating product: %s", err)
b3feb17 @esmil implement editing products
esmil authored
386 return 'MAIN'
387 end
388
389 product_dump(assert(db:fetchone(
390 "SELECT name, price FROM products WHERE id = ?", product.id)))
391 return 'MAIN'
392 end,
393 }
394
626582e @esmil use all uppercase names for states
esmil authored
395 USER = {
f175cf5 @esmil initial commit
esmil authored
396 wait = timeout,
07467f5 @esmil display menus and clear screen automatically
esmil authored
397 timeout = idle,
f175cf5 @esmil initial commit
esmil authored
398
399 card = login,
400
79b00b2 @esmil allow multiple purchases
esmil authored
401 barcode = function(code, id, count)
f175cf5 @esmil initial commit
esmil authored
402 local r = assert(db:fetchone("\z
403 SELECT id, name, price \z
404 FROM products \z
405 WHERE barcode = ?", code))
406
407 if r == true then
07467f5 @esmil display menus and clear screen automatically
esmil authored
408 print " Unknown product.."
626582e @esmil use all uppercase names for states
esmil authored
409 return 'USER', id
f175cf5 @esmil initial commit
esmil authored
410 end
411
412 local pid = r[1]
413 local price = r[3]
414
79b00b2 @esmil allow multiple purchases
esmil authored
415 if count then
416 print(" Buying %s for %d * %.2f = %.2f DKK",
417 r[2], count, price, count * price)
418 else
419 print(" Buying %s for %.2f DKK", r[2], price)
420 count = 1
421 end
f175cf5 @esmil initial commit
esmil authored
422
423 assert(db:exec("\z
424 BEGIN; \z
4764fba @esmil support transfers
esmil authored
425 UPDATE users SET balance = balance - @count * @amount WHERE id = @id; \z
426 INSERT INTO log (dt, uid, oid, count, amount) \z
427 VALUES (datetime('now'), @id, @oid, @count, @amount); \z
428 COMMIT", { id = id, oid = pid, count = count, amount = price }))
f175cf5 @esmil initial commit
esmil authored
429
430 r = assert(db:fetchone(
640f547 @esmil update database schema
esmil authored
431 "SELECT balance FROM users WHERE id = ?", id))
07467f5 @esmil display menus and clear screen automatically
esmil authored
432 print(" New balance: %.2f DKK", r[1])
f175cf5 @esmil initial commit
esmil authored
433
626582e @esmil use all uppercase names for states
esmil authored
434 return 'USER', id
f175cf5 @esmil initial commit
esmil authored
435 end,
436
437 keyboard = {
79b00b2 @esmil allow multiple purchases
esmil authored
438 ['/'] = function(id)
14bcf26 @esmil add switch card functionality
esmil authored
439 print " Swipe new card (or press enter to abort):"
440 return 'SWITCH_CARD', id
441 end,
4764fba @esmil support transfers
esmil authored
442 ['*'] = function(id)
ea75e9d @esmil show user logs
esmil authored
443 local r = assert(db:fetchall("\z
4764fba @esmil support transfers
esmil authored
444 SELECT substr(dt,6,11), \z
445 CASE \z
446 WHEN count NOT NULL THEN oname \z
447 WHEN oid <> ?1 THEN 'Transfer to ' || oname \z
448 WHEN uid <> ?1 THEN 'Transfer from ' || uname \z
449 WHEN amount >= 0 THEN 'Deposit' \z
450 ELSE 'Withdrawal' END, \z
451 CASE WHEN count NOT NULL THEN count \z
452 WHEN oid <> ?1 THEN 1 ELSE -1 END, \z
453 amount \z
454 FROM full_log \z
455 WHERE uid = ?1 OR (count IS NULL AND oid = ?1) \z
456 ORDER BY dt DESC LIMIT 38", id))
ea75e9d @esmil show user logs
esmil authored
457
458 for i = #r, 1, -1 do
459 local row = r[i]
4764fba @esmil support transfers
esmil authored
460 if row[3] == 1 or row[3] == -1 then
461 print("%s %s %8.2f DKK",
6dbd682 Adjustment to screen size.
Unknown authored
462 row[1], row[2]:utf8trim(23), -row[3]*row[4])
ea75e9d @esmil show user logs
esmil authored
463 else
4764fba @esmil support transfers
esmil authored
464 print("%s %s %4d * %6.2f %8.2f DKK",
465 row[1], row[2]:utf8trim(22), row[3], row[4], -row[3]*row[4])
ea75e9d @esmil show user logs
esmil authored
466 end
467 end
468
469 return 'USER', id
470 end,
4764fba @esmil support transfers
esmil authored
471 ['+'] = function(id)
472 print " Enter amount (or press enter to abort):"
473 return 'DEPOSIT', id
474 end,
79b00b2 @esmil allow multiple purchases
esmil authored
475 ['-'] = function(id)
4764fba @esmil support transfers
esmil authored
476 print " Enter user id (or press enter for user list):"
477 return 'TRANSFER_LIST', id, 0
478 end,
479 ['.'] = function(id)
79b00b2 @esmil allow multiple purchases
esmil authored
480 user_menu()
481 return 'USER', id
482 end,
483 ['n'] = function(id)
484 print " Sigh. A number. That is [1-9][0-9]*"
485 return 'USER', id
486 end,
487 [''] = function(id, count)
488 if count then
489 print " Aborted."
490 return 'USER', id
491 end
492
493 return idle()
494 end,
11365d3 @knielsen Allow to enter barcode explicitly with /<barcode>.
knielsen authored
495 function(cmd, id, count) --default
496 if cmd:sub(1,1) == '/' then
497 return (USER['barcode'])(cmd:sub(2), id, count)
498 end
79b00b2 @esmil allow multiple purchases
esmil authored
499 local count = tonumber(cmd)
500 if count then
501 print(" Buying %d of the next thing scanned. Press ENTER to abort.",
502 count)
503 return 'USER', id, count
504 end
505
07467f5 @esmil display menus and clear screen automatically
esmil authored
506 print(" Unknown command '%s'.", cmd)
507 user_menu()
626582e @esmil use all uppercase names for states
esmil authored
508 return 'USER', id
f175cf5 @esmil initial commit
esmil authored
509 end,
510 },
511 }
512
4764fba @esmil support transfers
esmil authored
513 SWITCH_CARD = {
514 wait = timeout,
515 timeout = function(_, id)
516 print " Aborted due to inactivity."
517 return 'USER', id
518 end,
519
520 card = function(hash, id)
521 print "Updating hash.."
522 local ok, err = db:fetchone(
523 "UPDATE users SET hash = ? WHERE id = ?", hash, id)
524 if not ok then
525 print("Error updating hash: %s", err)
526 else
527 print("Done.")
528 end
529
530 return 'USER', id
531 end,
532
533 barcode = 'SWITCH_CARD',
534
535 keyboard = function(_, id)
536 print " Aborted."
537 return 'USER', id
538 end,
539 }
540
626582e @esmil use all uppercase names for states
esmil authored
541 DEPOSIT = {
f175cf5 @esmil initial commit
esmil authored
542 wait = timeout,
543 timeout = function(_, id)
07467f5 @esmil display menus and clear screen automatically
esmil authored
544 print " Aborted due to inactivity."
626582e @esmil use all uppercase names for states
esmil authored
545 return 'USER', id
f175cf5 @esmil initial commit
esmil authored
546 end,
547
548 card = login,
549
626582e @esmil use all uppercase names for states
esmil authored
550 barcode = 'DEPOSIT',
f175cf5 @esmil initial commit
esmil authored
551
552 keyboard = {
553 [''] = function(id)
07467f5 @esmil display menus and clear screen automatically
esmil authored
554 print " Aborted."
626582e @esmil use all uppercase names for states
esmil authored
555 return 'USER', id
f175cf5 @esmil initial commit
esmil authored
556 end,
557 function(amount, id) --default
558 local n = tonumber(amount)
559 if not n then
07467f5 @esmil display menus and clear screen automatically
esmil authored
560 print(" Unable to parse '%s', try again (or press enter to abort):", amount)
626582e @esmil use all uppercase names for states
esmil authored
561 return 'DEPOSIT', id
f175cf5 @esmil initial commit
esmil authored
562 end
4764fba @esmil support transfers
esmil authored
563 if n >= 0 then
564 print(" Inserting %.2f DKK", n)
565 else
566 print(" Withdrawing %.2f DKK", -n)
567 end
f175cf5 @esmil initial commit
esmil authored
568
03f530f @esmil log deposits too
esmil authored
569 assert(db:exec("\z
570 BEGIN; \z
571 UPDATE users SET balance = balance + @amount WHERE id = @id; \z
4764fba @esmil support transfers
esmil authored
572 INSERT INTO log (dt, uid, oid, count, amount) \z
573 VALUES (datetime('now'), @id, @id, NULL, @amount); \z
03f530f @esmil log deposits too
esmil authored
574 COMMIT", { id = id, amount = n }))
f175cf5 @esmil initial commit
esmil authored
575
a52e82d @esmil fix formatting and variable leak
esmil authored
576 local r = assert(db:fetchone(
577 "SELECT balance FROM users WHERE id = ?", id))
07467f5 @esmil display menus and clear screen automatically
esmil authored
578 print(" New balance: %.2f DKK", r[1])
f175cf5 @esmil initial commit
esmil authored
579
626582e @esmil use all uppercase names for states
esmil authored
580 return 'USER', id
f175cf5 @esmil initial commit
esmil authored
581 end,
582 },
583 }
584
4764fba @esmil support transfers
esmil authored
585 TRANSFER_LIST = {
14bcf26 @esmil add switch card functionality
esmil authored
586 wait = timeout,
587 timeout = function(_, id)
588 print " Aborted due to inactivity."
589 return 'USER', id
590 end,
591
4764fba @esmil support transfers
esmil authored
592 card = login,
14bcf26 @esmil add switch card functionality
esmil authored
593
4764fba @esmil support transfers
esmil authored
594 barcode = 'TRANSFER_LIST',
14bcf26 @esmil add switch card functionality
esmil authored
595
4764fba @esmil support transfers
esmil authored
596 keyboard = {
597 [''] = function(id, offset)
598 local r = assert(db:fetchall(
6dbd682 Adjustment to screen size.
Unknown authored
599 "SELECT id, name FROM users ORDER BY id LIMIT 29 OFFSET ?", offset))
4764fba @esmil support transfers
esmil authored
600 local n = #r
601 if n == 0 then
602 print " Aborted."
603 return 'USER', id
604 end
14bcf26 @esmil add switch card functionality
esmil authored
605
4764fba @esmil support transfers
esmil authored
606 for i = 1, n < 39 and n or 38 do
607 local row = r[i]
608 print(" %4d) %s", row[1], row[2])
609 end
610
611 if n < 39 then
612 print " Enter user id (or press enter to abort):"
613 else
614 print " Enter user id (or press enter to continue list):"
615 end
6dbd682 Adjustment to screen size.
Unknown authored
616 return 'TRANSFER_LIST', id, offset + 29
4764fba @esmil support transfers
esmil authored
617 end,
618 function(cmd, id) --default
619 local n = tonumber(cmd)
620 if not n then
621 print(" Unable to parse '%s', aborted.", cmd)
622 return 'USER', id
623 end
624
625 local r = assert(db:fetchone(
626 "SELECT name FROM users WHERE id = ?", n))
627 if r == true then
628 print(" No such user. Aborted.")
629 return 'USER', id
630 end
631
632 print(" Enter amount to transfer to %s (or press enter to abort):", r[1])
633 return 'TRANSFER_AMOUNT', id, n
634 end,
635 },
636 }
637
638 TRANSFER_AMOUNT = {
639 wait = timeout,
640 timeout = function(_, id)
641 print " Aborted due to inactivity."
14bcf26 @esmil add switch card functionality
esmil authored
642 return 'USER', id
643 end,
4764fba @esmil support transfers
esmil authored
644
645 card = login,
646
647 barcode = 'TRANSFER_AMOUNT',
648
649 keyboard = {
650 [''] = function(id)
651 print " Aborted."
652 return 'USER', id
653 end,
654 function(cmd, id, oid) --default
655 local n = tonumber(cmd)
656 if not n then
657 print(" Unable to parse '%s', aborted.", cmd)
658 return 'USER', id
659 end
660
661 if n <= 0 then
662 print " Fark you.. Aborted."
663 return 'USER', id
664 end
665
666 assert(db:exec("\z
667 BEGIN; \z
668 UPDATE users SET balance = balance - @amount WHERE id = @id; \z
669 UPDATE users SET balance = balance + @amount WHERE id = @oid; \z
670 INSERT INTO log (dt, uid, oid, count, amount) \z
671 VALUES (datetime('now'), @id, @oid, NULL, @amount); \z
672 COMMIT", { id = id, oid = oid, amount = n }))
673
674 r = assert(db:fetchone(
675 "SELECT balance FROM users WHERE id = ?", id))
676 print(" New balance: %.2f DKK", r[1])
677
678 return 'USER', id
679 end,
680 },
14bcf26 @esmil add switch card functionality
esmil authored
681 }
682
27a3610 @esmil cleanup and move "engine" to bottom
esmil authored
683 --- the "engine" ---
684
685 -- all input events goes through this queue
686 local input = bqueue.new()
687
688 -- spawn coroutines to read from
689 -- inputs and add to the input queue
690 utils.spawn(function()
145242f @esmil update to latest LEM
esmil authored
691 local stdin = io.stdin
27a3610 @esmil cleanup and move "engine" to bottom
esmil authored
692 while true do
693 local line = assert(stdin:read('*l'))
694 input:put{ from = 'keyboard', data = line }
695 end
696 end)
697
698 utils.spawn(function()
145242f @esmil update to latest LEM
esmil authored
699 local ins = assert(io.open(arg[2] or 'card', 'r'))
27a3610 @esmil cleanup and move "engine" to bottom
esmil authored
700 local ctx = sha1.new()
701 while true do
702 local line = assert(ins:read('*l', '\r'))
703 input:put{ from = 'card', data = ctx:add(line):add('\r'):hex() }
704 end
705 end)
706
707 utils.spawn(function()
145242f @esmil update to latest LEM
esmil authored
708 local ins = assert(io.open(arg[3] or 'barcode', 'r'))
27a3610 @esmil cleanup and move "engine" to bottom
esmil authored
709 while true do
710 local line = assert(ins:read('*l', '\r'))
711 input:put{ from = 'barcode', data = line }
712 end
713 end)
714
715 -- this is function reads events from the
716 -- input queue and "runs" the state machine
07467f5 @esmil display menus and clear screen automatically
esmil authored
717 local function run(...)
f175cf5 @esmil initial commit
esmil authored
718 local valid_sender = {
719 timeout = true,
720 card = true,
721 barcode = true,
722 keyboard = true
723 }
724
725 local handle_state
726
727 local lookup = {
728 ['string'] = function(s, data, ...) return handle_state(s, ...) end,
729 ['function'] = function(f, data, ...) return handle_state(f(data, ...)) end,
730 ['table'] = function(t, data, ...)
731 local f = t[data]
732 if f then return handle_state(f(...)) end
733 f = assert(t[1], 'no default handler found')
734 return handle_state(f(data, ...))
735 end,
736 }
737
738 function handle_state(str, ...)
739 local state = _ENV[str]
740 if not state then
741 error(format("%s: invalid state", tostring(str)))
742 end
743
744 local cmd, err = input:get(state.wait)
745 if not cmd then
746 if err == 'timeout' then
747 cmd = { from = 'timeout', data = 'timeout' }
748 else
749 error(err)
750 end
751 end
752
753 if not valid_sender[cmd.from] then
754 error(format("%s: spurirous command from '%s'", str, tostring(cmd.from)))
755 end
756
757 local edge = state[cmd.from]
758 if not edge then
759 error(format("%s: no edge defined for '%s'", str, cmd.from))
760 end
761
762 local handler = lookup[type(edge)]
763 if not handler then
764 error(format("%s: invalid edge '%s'", str, cmd.from))
765 end
766
767 return handler(edge, cmd.data, ...)
768 end
769
07467f5 @esmil display menus and clear screen automatically
esmil authored
770 return handle_state(...)
f175cf5 @esmil initial commit
esmil authored
771 end
772
07467f5 @esmil display menus and clear screen automatically
esmil authored
773 return run(idle())
f175cf5 @esmil initial commit
esmil authored
774
775 -- vim: set ts=2 sw=2 noet:
Something went wrong with that request. Please try again.