File size: 2,404 Bytes
663ec84 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | """Small, thread-safe shared-recognition quota; stores timestamps, never keys."""
from contextlib import contextmanager
from pathlib import Path
import sqlite3
import threading
import time
class SharedLimit(Exception):
pass
class SharedUsage:
def __init__(self,path,daily=200,per_minute=20,concurrent=2):
self.path=Path(path);self.daily=daily;self.per_minute=per_minute
self.slots=threading.BoundedSemaphore(concurrent)
self.path.parent.mkdir(parents=True,exist_ok=True)
with sqlite3.connect(self.path) as db:
db.execute('CREATE TABLE IF NOT EXISTS recognition_usage (started REAL NOT NULL)')
db.execute('CREATE INDEX IF NOT EXISTS recognition_started ON recognition_usage(started)')
def summary(self,now=None):
now=time.time() if now is None else now
with sqlite3.connect(self.path) as db:
used=db.execute('SELECT COUNT(*) FROM recognition_usage WHERE started>?',(now-86400,)).fetchone()[0]
return {'per_24_hours':self.daily,'per_minute':self.per_minute,'used_24_hours':used,'remaining':max(0,self.daily-used)}
def reserve(self,now=None):
now=time.time() if now is None else now
with sqlite3.connect(self.path,timeout=10,isolation_level=None) as db:
db.execute('BEGIN IMMEDIATE')
try:
db.execute('DELETE FROM recognition_usage WHERE started<=?',(now-86400,))
daily=db.execute('SELECT COUNT(*) FROM recognition_usage').fetchone()[0]
minute=db.execute('SELECT COUNT(*) FROM recognition_usage WHERE started>?',(now-60,)).fetchone()[0]
if daily>=self.daily:raise SharedLimit('The classroom’s shared recognition allowance has been used for now. Please try again later or ask the teacher.')
if minute>=self.per_minute:raise SharedLimit('The classroom is making lots of guesses. Please wait a minute and try again.')
db.execute('INSERT INTO recognition_usage(started) VALUES(?)',(now,));db.execute('COMMIT')
except Exception:
db.execute('ROLLBACK');raise
@contextmanager
def lease(self,now=None):
if not self.slots.acquire(blocking=False):raise SharedLimit('Other drawings are being recognized. Please try again in a moment.')
try:
self.reserve(now);yield
finally:self.slots.release()
|