# # Copyright (c) Daniel Sheffield 2023 # All rights reserved # # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY import os from threading import Thread from typing import Union from bottle import ( route, request, response, static_file, redirect, FormsDict, HTTPResponse, ) from psycopg import Cursor, connect from psycopg.rows import TupleRow from .route_decorators import normalize, poison, cursor from .query_to_xml import get_categories, get_groups, get_products, get_tags from .CachedLoadingPage import CachedLoadingPage from .Cache import Cache from . import trend as worker from time import sleep host = f"host={os.getenv('HOST')}" db = f"dbname={os.getenv('DB', 'grocery')}" user = f"user={os.getenv('USER', 'das')}" password = f"password={os.getenv('PASSWORD','')}" if not password.split('=',1)[1]: password = '' conn = connect(f"{host} {db} {user} {password}") @route('/grocery/static/') def send_static(filename): return static_file(filename, root='app/rest/static') def trend_thread(conn, path, forms): def cb(queue): return Thread(target=worker.trend, args=( queue, conn, path, forms )).start() return cb @route('/grocery/trend', method=['GET', 'POST']) @poison(cache=Cache(10)) @normalize def trend(key: str, forms: FormsDict, cache: Cache): _, _, path, *_ = request.urlparts page = cache[key] if page is None: page = cache.add(key, CachedLoadingPage([], trend_thread(conn, path, forms))) for i in iter_page(page): yield i sleep(0.5) def iter_page(page): # copy first to avoid races resp = list(page.value) pos = len(resp) yield ''.join(resp) while not page.loaded: page.update() # all changes since last yield resp = list(page.value[pos:]) pos = pos + len(resp) yield ''.join(resp) # possibly have not yielded the entire page if pos < len(page.value): yield ''.join(page.value[pos:]) @route('/grocery/groups', method=['GET', 'POST']) @poison(cache=Cache(10)) @normalize @cursor(connection=conn) def groups(cur: Cursor[TupleRow], key: Union[int, str], forms: FormsDict, cache: Cache): response.content_type = 'application/xhtml+xml; charset=utf-8' return get_groups(cur, forms) @route('/grocery/categories', method=['GET', 'POST']) @poison(cache=Cache(10)) @normalize @cursor(connection=conn) def categories(cur: Cursor[TupleRow], key: Union[int, str], forms: FormsDict, cache: Cache): response.content_type = 'application/xhtml+xml; charset=utf-8' return get_categories(cur, forms) @route('/grocery/products', method=['GET', 'POST']) @poison(cache=Cache(10)) @normalize @cursor(connection=conn) def products(cur: Cursor[TupleRow], key: Union[int, str], forms: FormsDict, cache: Cache): response.content_type = 'application/xhtml+xml; charset=utf-8' return get_products(cur, forms) @route('/grocery/tags', method=['GET', 'POST']) @poison(cache=Cache(10)) @normalize @cursor(connection=conn) def tags(cur: Cursor[TupleRow], key: Union[int, str], forms: FormsDict, cache: Cache): response.content_type = 'application/xhtml+xml; charset=utf-8' return get_tags(cur, forms)