|
@@ -3,8 +3,9 @@
|
|
# All rights reserved
|
|
# All rights reserved
|
|
#
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
|
|
# THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
|
|
-from typing import Iterable
|
|
|
|
|
|
+from typing import Iterable, Dict
|
|
import os
|
|
import os
|
|
|
|
+from time import time
|
|
from urllib.parse import urlencode
|
|
from urllib.parse import urlencode
|
|
from bottle import (
|
|
from bottle import (
|
|
route,
|
|
route,
|
|
@@ -17,11 +18,13 @@ from bottle import (
|
|
static_file,
|
|
static_file,
|
|
TEMPLATE_PATH,
|
|
TEMPLATE_PATH,
|
|
)
|
|
)
|
|
-from matplotlib.axes import Axes
|
|
|
|
|
|
+import matplotlib
|
|
from psycopg import connect
|
|
from psycopg import connect
|
|
from psycopg.sql import SQL, Literal
|
|
from psycopg.sql import SQL, Literal
|
|
import seaborn as sns
|
|
import seaborn as sns
|
|
-from multiprocessing import Lock
|
|
|
|
|
|
+from threading import Lock, Thread
|
|
|
|
+
|
|
|
|
+from queue import Queue, Empty
|
|
|
|
|
|
from ..data.filter import(
|
|
from ..data.filter import(
|
|
get_filter,
|
|
get_filter,
|
|
@@ -30,12 +33,13 @@ from ..data.filter import(
|
|
from ..data.util import(
|
|
from ..data.util import(
|
|
get_where_include_exclude
|
|
get_where_include_exclude
|
|
)
|
|
)
|
|
-from .trend import trend_internal
|
|
|
|
|
|
+from . import trend as worker
|
|
from . import PARAMS
|
|
from . import PARAMS
|
|
-import matplotlib
|
|
|
|
|
|
+from .CachedLoadingPage import CachedLoadingPage
|
|
|
|
+
|
|
matplotlib.use('agg')
|
|
matplotlib.use('agg')
|
|
|
|
|
|
-CACHE = dict()
|
|
|
|
|
|
+CACHE: Dict[str, CachedLoadingPage] = dict()
|
|
|
|
|
|
host = f"host={os.getenv('HOST')}"
|
|
host = f"host={os.getenv('HOST')}"
|
|
db = f"dbname={os.getenv('DB', 'grocery')}"
|
|
db = f"dbname={os.getenv('DB', 'grocery')}"
|
|
@@ -89,7 +93,6 @@ def normalize_query(query: FormsDict, allow: Iterable[str] = None) -> str:
|
|
])
|
|
])
|
|
|
|
|
|
|
|
|
|
-
|
|
|
|
@route('/grocery/static/<filename:path>')
|
|
@route('/grocery/static/<filename:path>')
|
|
def send_static(filename):
|
|
def send_static(filename):
|
|
return static_file(filename, root='app/rest/static')
|
|
return static_file(filename, root='app/rest/static')
|
|
@@ -106,33 +109,32 @@ def trend():
|
|
if request.query_string != normalized:
|
|
if request.query_string != normalized:
|
|
return redirect(f'{path}?{normalized}')
|
|
return redirect(f'{path}?{normalized}')
|
|
|
|
|
|
- loading = template("loading", progress=[])
|
|
|
|
if request.query_string in CACHE:
|
|
if request.query_string in CACHE:
|
|
- if LOCK.acquire(block=False):
|
|
|
|
- try:
|
|
|
|
- return CACHE[request.query_string]["state"]
|
|
|
|
- finally:
|
|
|
|
- try:
|
|
|
|
- CACHE[request.query_string]["state"] = next(CACHE[request.query_string]["iter"])
|
|
|
|
- except StopIteration:
|
|
|
|
- del CACHE[request.query_string]
|
|
|
|
- finally:
|
|
|
|
- LOCK.release()
|
|
|
|
|
|
+ page = CACHE[request.query_string]
|
|
|
|
+ if not page.stale:
|
|
|
|
+ if not page.loaded:
|
|
|
|
+ return page.update()
|
|
|
|
+ return page.value
|
|
else:
|
|
else:
|
|
- return CACHE[request.query_string]["state"]
|
|
|
|
-
|
|
|
|
- if LOCK.acquire(block=False):
|
|
|
|
|
|
+ del CACHE[request.query_string]
|
|
|
|
+
|
|
|
|
+ if LOCK.acquire(blocking=True):
|
|
if request.query_string in CACHE:
|
|
if request.query_string in CACHE:
|
|
- LOCK.release()
|
|
|
|
- return CACHE[request.query_string]["state"]
|
|
|
|
|
|
+ page = CACHE[request.query_string]
|
|
|
|
+ if not page.stale:
|
|
|
|
+ LOCK.release()
|
|
|
|
+ if not page.loaded:
|
|
|
|
+ return page.update()
|
|
|
|
+ return page.value
|
|
|
|
+
|
|
try:
|
|
try:
|
|
- CACHE[request.query_string] = {
|
|
|
|
- "iter": trend_internal(conn, path, request.query),
|
|
|
|
- "state": loading,
|
|
|
|
- }
|
|
|
|
|
|
+ page = CachedLoadingPage(template("loading", progress=[]))
|
|
|
|
+ CACHE[request.query_string] = page
|
|
|
|
+ thread = Thread(target=worker.trend, args=(page.queue, conn, path, request.query))
|
|
|
|
+ thread.start()
|
|
|
|
+ return page.value
|
|
finally:
|
|
finally:
|
|
LOCK.release()
|
|
LOCK.release()
|
|
- return loading
|
|
|
|
|
|
|
|
|
|
|
|
@route('/grocery/groups')
|
|
@route('/grocery/groups')
|
|
@@ -242,4 +244,4 @@ SELECT query_to_xml_and_xmlschema({inner}, false, false, ''::text)
|
|
response.content_type = 'application/xhtml+xml; charset=utf-8'
|
|
response.content_type = 'application/xhtml+xml; charset=utf-8'
|
|
return template("query-to-xml", title="Tags", xml=xml, form=form)
|
|
return template("query-to-xml", title="Tags", xml=xml, form=form)
|
|
|
|
|
|
-run(host='0.0.0.0', port=6772, server='gunicorn')
|
|
|
|
|
|
+run(host='0.0.0.0', port=6772, server='paste')
|