2019-01-18 10:53:23 +01:00
|
|
|
"""
|
|
|
|
General projector code.
|
|
|
|
|
|
|
|
Functions that handel the registration of projector elements and the rendering
|
|
|
|
of the data to present it on the projector.
|
|
|
|
"""
|
|
|
|
|
2020-05-15 11:47:43 +02:00
|
|
|
from collections import defaultdict
|
|
|
|
from typing import Any, Awaitable, Callable, Dict, List, Optional
|
2015-02-18 01:45:39 +01:00
|
|
|
|
2020-05-15 11:47:43 +02:00
|
|
|
from . import logging
|
2018-12-23 11:05:38 +01:00
|
|
|
from .cache import element_cache
|
2015-02-18 01:45:39 +01:00
|
|
|
|
2016-02-27 20:25:06 +01:00
|
|
|
|
2020-05-15 11:47:43 +02:00
|
|
|
logger = logging.getLogger(__name__)
|
2015-06-12 21:08:57 +02:00
|
|
|
|
2017-08-30 00:07:54 +02:00
|
|
|
|
2019-01-27 13:17:17 +01:00
|
|
|
class ProjectorElementException(Exception):
|
2017-08-30 00:07:54 +02:00
|
|
|
"""
|
2019-01-27 13:17:17 +01:00
|
|
|
Exception for errors in one element on the projector.
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
2020-05-15 11:47:43 +02:00
|
|
|
class ProjectorAllDataProvider:
|
|
|
|
NON_EXISTENT_MARKER = object()
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
self.cache: Any = defaultdict(dict) # fuu you mypy
|
|
|
|
self.fetched_collection: Dict[str, bool] = {}
|
|
|
|
|
|
|
|
async def get(self, collection: str, id: int) -> Optional[Dict[str, Any]]:
|
|
|
|
cache_data = self.cache[collection].get(id)
|
|
|
|
if cache_data is None:
|
|
|
|
data: Any = await element_cache.get_element_data(collection, id)
|
|
|
|
if data is None:
|
|
|
|
data = ProjectorAllDataProvider.NON_EXISTENT_MARKER
|
|
|
|
self.cache[collection][id] = data
|
2020-05-15 18:24:21 +02:00
|
|
|
|
|
|
|
cache_data = self.cache[collection][id]
|
|
|
|
if cache_data == ProjectorAllDataProvider.NON_EXISTENT_MARKER:
|
2020-05-15 11:47:43 +02:00
|
|
|
return None
|
2020-05-15 18:24:21 +02:00
|
|
|
return cache_data
|
2020-05-15 11:47:43 +02:00
|
|
|
|
|
|
|
async def get_collection(self, collection: str) -> Dict[int, Dict[str, Any]]:
|
|
|
|
if not self.fetched_collection.get(collection, False):
|
|
|
|
collection_data = await element_cache.get_collection_data(collection)
|
|
|
|
self.cache[collection] = collection_data
|
|
|
|
self.fetched_collection[collection] = True
|
|
|
|
return self.cache[collection]
|
|
|
|
|
|
|
|
async def exists(self, collection: str, id: int) -> bool:
|
|
|
|
model = await self.get(collection, id)
|
|
|
|
return model is not None
|
|
|
|
|
|
|
|
|
|
|
|
ProjectorSlide = Callable[
|
|
|
|
[ProjectorAllDataProvider, Dict[str, Any], int], Awaitable[Dict[str, Any]]
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
projector_slides: Dict[str, ProjectorSlide] = {}
|
|
|
|
|
|
|
|
|
2019-01-27 13:17:17 +01:00
|
|
|
def register_projector_slide(name: str, slide: ProjectorSlide) -> None:
|
|
|
|
"""
|
|
|
|
Registers a projector slide.
|
2017-08-30 00:07:54 +02:00
|
|
|
|
|
|
|
Has to be called in the app.ready method.
|
|
|
|
"""
|
2019-01-27 13:17:17 +01:00
|
|
|
projector_slides[name] = slide
|
2017-08-30 00:07:54 +02:00
|
|
|
|
|
|
|
|
2019-01-10 15:06:10 +01:00
|
|
|
async def get_projector_data(
|
2019-10-29 15:05:52 +01:00
|
|
|
projector_ids: List[int] = None,
|
2019-01-18 19:11:22 +01:00
|
|
|
) -> Dict[int, List[Dict[str, Any]]]:
|
2018-12-23 11:05:38 +01:00
|
|
|
"""
|
2019-01-18 19:11:22 +01:00
|
|
|
Calculates and returns the data for one or all projectors.
|
2019-01-18 10:53:23 +01:00
|
|
|
|
|
|
|
The keys of the returned data are the projector ids as int. When converted
|
|
|
|
to json, the numbers will changed to strings like "1".
|
|
|
|
|
2019-01-18 19:11:22 +01:00
|
|
|
The data for each projector is a list of elements.
|
2019-01-18 10:53:23 +01:00
|
|
|
|
2019-01-18 19:11:22 +01:00
|
|
|
Each element is a dict where the keys are "elements", "data". "elements"
|
|
|
|
contains the projector elements. It is the same as the projector elements in
|
|
|
|
the database. "data" contains all necessary data to render the projector
|
2019-01-18 10:53:23 +01:00
|
|
|
element. The key can also be "error" if there is a generall error for the
|
2019-01-18 19:11:22 +01:00
|
|
|
slide. In this case the values "elements" and "data" are optional.
|
2019-01-18 10:53:23 +01:00
|
|
|
|
|
|
|
The returned value looks like this:
|
|
|
|
|
|
|
|
projector_data = {
|
2019-01-18 19:11:22 +01:00
|
|
|
1: [
|
|
|
|
{
|
|
|
|
"element": {
|
2019-01-18 10:53:23 +01:00
|
|
|
"name": "agenda/item-list",
|
|
|
|
},
|
|
|
|
"data": {
|
|
|
|
"items": []
|
|
|
|
},
|
|
|
|
},
|
2019-01-18 19:11:22 +01:00
|
|
|
],
|
2019-01-18 10:53:23 +01:00
|
|
|
}
|
2017-08-30 00:07:54 +02:00
|
|
|
"""
|
2018-12-23 11:05:38 +01:00
|
|
|
if projector_ids is None:
|
|
|
|
projector_ids = []
|
|
|
|
|
2019-01-18 19:11:22 +01:00
|
|
|
projector_data: Dict[int, List[Dict[str, Any]]] = {}
|
2020-05-15 11:47:43 +02:00
|
|
|
all_data_provider = ProjectorAllDataProvider()
|
|
|
|
projectors = await all_data_provider.get_collection("core/projector")
|
2018-12-23 11:05:38 +01:00
|
|
|
|
2020-05-15 11:47:43 +02:00
|
|
|
for projector_id, projector in projectors.items():
|
2018-12-23 11:05:38 +01:00
|
|
|
if projector_ids and projector_id not in projector_ids:
|
|
|
|
# only render the projector in question.
|
|
|
|
continue
|
|
|
|
|
2019-01-18 19:11:22 +01:00
|
|
|
if not projector["elements"]:
|
|
|
|
# Skip empty elements.
|
2018-12-23 11:05:38 +01:00
|
|
|
continue
|
|
|
|
|
2019-01-18 19:11:22 +01:00
|
|
|
projector_data[projector_id] = []
|
|
|
|
for element in projector["elements"]:
|
2019-01-27 13:17:17 +01:00
|
|
|
projector_slide = projector_slides[element["name"]]
|
|
|
|
try:
|
2020-05-15 11:47:43 +02:00
|
|
|
data = await projector_slide(all_data_provider, element, projector_id)
|
2019-01-27 13:17:17 +01:00
|
|
|
except ProjectorElementException as err:
|
2019-02-10 10:50:18 +01:00
|
|
|
data = {"error": str(err)}
|
2019-01-27 13:17:17 +01:00
|
|
|
projector_data[projector_id].append({"data": data, "element": element})
|
2019-01-18 19:11:22 +01:00
|
|
|
|
2018-12-23 11:05:38 +01:00
|
|
|
return projector_data
|
|
|
|
|
|
|
|
|
2020-05-15 11:47:43 +02:00
|
|
|
async def get_config(all_data_provider: ProjectorAllDataProvider, key: str) -> Any:
|
2017-08-30 00:07:54 +02:00
|
|
|
"""
|
2020-05-15 11:47:43 +02:00
|
|
|
Returns a config value from all_data_provider.
|
|
|
|
Triggers the cache early: It access `get_colelction` instead of `get`. It
|
|
|
|
allows for all successive queries for configs to be cached.
|
2018-12-23 11:05:38 +01:00
|
|
|
"""
|
|
|
|
from ..core.config import config
|
|
|
|
|
2019-03-23 12:06:57 +01:00
|
|
|
config_id = (await config.async_get_key_to_id())[key]
|
|
|
|
|
2020-05-15 11:47:43 +02:00
|
|
|
configs = await all_data_provider.get_collection(config.get_collection_string())
|
|
|
|
return configs[config_id]["value"]
|
2019-11-20 17:09:03 +01:00
|
|
|
|
|
|
|
|
2020-05-15 11:47:43 +02:00
|
|
|
async def get_model(
|
|
|
|
all_data_provider: ProjectorAllDataProvider, collection: str, id: Any
|
|
|
|
) -> Dict[str, Any]:
|
2019-11-20 17:09:03 +01:00
|
|
|
"""
|
|
|
|
Tries to get the model identified by the collection and id.
|
|
|
|
If the id is invalid or the model not found, ProjectorElementExceptions will be raised.
|
|
|
|
"""
|
|
|
|
if id is None:
|
|
|
|
raise ProjectorElementException(f"id is required for {collection} slide")
|
|
|
|
|
2020-05-15 11:47:43 +02:00
|
|
|
model = await all_data_provider.get(collection, id)
|
|
|
|
if model is None:
|
2019-11-20 17:09:03 +01:00
|
|
|
raise ProjectorElementException(f"{collection} with id {id} does not exist")
|
|
|
|
return model
|
2020-02-13 18:24:51 +01:00
|
|
|
|
|
|
|
|
2020-05-15 11:47:43 +02:00
|
|
|
async def get_models(
|
|
|
|
all_data_provider: ProjectorAllDataProvider, collection: str, ids: List[Any]
|
2020-02-13 18:24:51 +01:00
|
|
|
) -> List[Dict[str, Any]]:
|
|
|
|
"""
|
|
|
|
Tries to fetch all given models. Models are required to be all of the collection `collection`.
|
|
|
|
"""
|
2020-05-15 11:47:43 +02:00
|
|
|
logger.info(
|
|
|
|
f"Note: a call to `get_models` with {collection}/{ids}. This might be cache-intensive"
|
|
|
|
)
|
|
|
|
return [await get_model(all_data_provider, collection, id) for id in ids]
|