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()