Skip to content

Commit

Permalink
Add notification sound
Browse files Browse the repository at this point in the history
  • Loading branch information
exflikt committed Oct 21, 2024
1 parent 7f29844 commit 216dc48
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 30 deletions.
25 changes: 15 additions & 10 deletions app/routers/placements.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from fastapi.responses import HTMLResponse
from sse_starlette.sse import EventSourceResponse

from ..store.placement import ModifiedFlag
from .. import templates
from ..store import (
PlacementTable,
Expand Down Expand Up @@ -40,13 +41,14 @@ async def _placed_items_incoming_stream(request: Request):
yield dict(data=content)
try:
while True:
async with PlacementTable.modified:
await PlacementTable.modified.wait()
async with PlacementTable.modified_cond_flag:
flag = await PlacementTable.modified_cond_flag.wait()
if flag & (ModifiedFlag.INCOMING | ModifiedFlag.PUT_BACK):
template = templates.placed_items_incoming.component_with_sound
else:
template = templates.placed_items_incoming.component
placed_items = await load_placed_items_incoming()
content = templates.placed_items_incoming.component(
request, placed_items
)
yield dict(data=content)
yield dict(data=template(request, placed_items))
except asyncio.CancelledError:
yield dict(event="shutdown", data="")
finally:
Expand Down Expand Up @@ -80,11 +82,14 @@ async def _incoming_placements_stream(
yield dict(data=content)
try:
while True:
async with PlacementTable.modified:
await PlacementTable.modified.wait()
async with PlacementTable.modified_cond_flag:
flag = await PlacementTable.modified_cond_flag.wait()
if flag & (ModifiedFlag.INCOMING | ModifiedFlag.PUT_BACK):
template = templates.incoming_placements.component_with_sound
else:
template = templates.incoming_placements.component
placements = await load_incoming_placements()
content = templates.incoming_placements.component(request, placements)
yield dict(data=content)
yield dict(data=template(request, placements))
except asyncio.CancelledError:
yield dict(event="shutdown", data="")
finally:
Expand Down
35 changes: 22 additions & 13 deletions app/store/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from . import placed_item, placement, product
from ._helper import _colname
from .placed_item import PlacedItem
from .placement import Placement
from .placement import ModifiedFlag, Placement
from .product import Product

DATABASE_URL = "sqlite:///db/app.db"
Expand Down Expand Up @@ -341,28 +341,37 @@ async def supply_all_and_complete(placement_id: int):
async with database.transaction():
await PlacedItemTable._supply_all(placement_id)
await PlacementTable._complete(placement_id)
async with PlacementTable.modified:
PlacementTable.modified.notify_all()
async with PlacementTable.modified_cond_flag:
FLAG = ModifiedFlag.SUPPLIED | ModifiedFlag.RESOLVED
PlacementTable.modified_cond_flag.notify_all(FLAG)


async def supply_and_complete_placement_if_done(placement_id: int, product_id: int):
async with database.transaction():
await PlacedItemTable._supply(placement_id, product_id)

update_query = sqlmodel.update(Placement).where(
(col(Placement.placement_id) == placement_id)
& sqlmodel.select(
sqlmodel.func.count(col(PlacedItem.item_no))
== sqlmodel.func.count(col(PlacedItem.supplied_at))
update_query = (
sqlmodel.update(Placement)
.where(
(col(Placement.placement_id) == placement_id)
& sqlmodel.select(
sqlmodel.func.count(col(PlacedItem.item_no))
== sqlmodel.func.count(col(PlacedItem.supplied_at))
)
.where(col(PlacedItem.placement_id) == placement_id)
.scalar_subquery()
)
.where(col(PlacedItem.placement_id) == placement_id)
.scalar_subquery()
.returning(col(Placement.placement_id).isnot(None))
)

values = {"completed_at": datetime.now(timezone.utc)}
await database.execute(update_query, values)
completed: bool | None = await database.fetch_val(update_query, values)

async with PlacementTable.modified:
PlacementTable.modified.notify_all()
async with PlacementTable.modified_cond_flag:
flag = ModifiedFlag.SUPPLIED
if completed is not None:
flag |= ModifiedFlag.RESOLVED
PlacementTable.modified_cond_flag.notify_all(flag)


async def _startup_db() -> None:
Expand Down
46 changes: 39 additions & 7 deletions app/store/placement.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import asyncio
from datetime import datetime, timezone
from enum import Flag, auto
from typing import Annotated

import sqlalchemy
Expand Down Expand Up @@ -28,17 +29,48 @@ class Placement(sqlmodel.SQLModel, table=True):
)


class ModifiedFlag(Flag):
ORIGINAL = auto()
INCOMING = auto()
SUPPLIED = auto()
RESOLVED = auto()
PUT_BACK = auto()


class ModifiedCondFlag:
_condvar: asyncio.Condition = asyncio.Condition()
flag: ModifiedFlag = ModifiedFlag.ORIGINAL

async def __aenter__(self):
await self._condvar.__aenter__()

async def __aexit__(self, exc_type, exc, tb) -> None:
await self._condvar.__aexit__(exc_type, exc, tb)

async def wait(self) -> ModifiedFlag:
await self._condvar.wait()
flag = self.flag
if len(self._condvar._waiters) == 0:
self.flag = ModifiedFlag.ORIGINAL
return flag

def notify_all(self, flag: ModifiedFlag | None = None):
self._condvar.notify_all()
if flag is not None:
self.flag |= flag


class Table:
modified: asyncio.Condition = asyncio.Condition()
modified_cond_flag = ModifiedCondFlag()

def __init__(self, database: Database):
self._db = database

async def insert(self, placement_id: int) -> None:
query = sqlmodel.insert(Placement)
await self._db.execute(query, {"placement_id": placement_id})
async with self.modified:
self.modified.notify_all()
async with self.modified_cond_flag:
self.modified_cond_flag.notify_all(ModifiedFlag.INCOMING)

@staticmethod
def _update(placement_id: int) -> sqlalchemy.Update:
Expand All @@ -48,8 +80,8 @@ def _update(placement_id: int) -> sqlalchemy.Update:
async def cancel(self, placement_id: int) -> None:
values = {"canceled_at": datetime.now(timezone.utc), "completed_at": None}
await self._db.execute(self._update(placement_id), values)
async with self.modified:
self.modified.notify_all()
async with self.modified_cond_flag:
self.modified_cond_flag.notify_all(ModifiedFlag.RESOLVED)

async def _complete(self, placement_id: int) -> None:
"""
Expand All @@ -62,8 +94,8 @@ async def _complete(self, placement_id: int) -> None:
async def reset(self, placement_id: int) -> None:
values = {"canceled_at": None, "completed_at": None}
await self._db.execute(self._update(placement_id), values)
async with self.modified:
self.modified.notify_all()
async with self.modified_cond_flag:
self.modified_cond_flag.notify_all(ModifiedFlag.PUT_BACK)

async def by_placement_id(self, placement_id: int) -> Placement | None:
query = sqlmodel.select(Placement).where(Placement.placement_id == placement_id)
Expand Down
8 changes: 8 additions & 0 deletions app/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ def page(placed_items: list[placed_item_t]): ...
@staticmethod
def component(placed_items: list[placed_item_t]): ...

@macro_template("placed-items-incoming.html", "component_with_sound")
@staticmethod
def component_with_sound(placed_items: list[placed_item_t]): ...


class incoming_placements: # namespace
@macro_template("incoming-placements.html")
Expand All @@ -122,6 +126,10 @@ def page(placements: list[placement_t]): ...
@staticmethod
def component(placements: list[placement_t]): ...

@macro_template("incoming-placements.html", "component_with_sound")
@staticmethod
def component_with_sound(placements: list[placement_t]): ...


class resolved_placements: # namespace
@macro_template("resolved-placements.html")
Expand Down
5 changes: 5 additions & 0 deletions app/templates/incoming-placements.html
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,8 @@ <h2 class="text-2xl">#{{ placement.placement_id }}</h2>
</div>
{% endfor %}
{% endmacro %}

{% macro component_with_sound(placements) %}
<audio src="{{ url_for('static', path='notification-1.mp3') }}" autoplay hidden></audio>
{{ component(placements) }}
{% endmacro %}
5 changes: 5 additions & 0 deletions app/templates/placed-items-incoming.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,8 @@ <h3 class="text-lg ml-1">{{ placed_item.name }}</h3>
</div>
{% endfor %}
{% endmacro %}

{% macro component_with_sound(placed_items) %}
<audio src="{{ url_for('static', path='notification-1.mp3') }}" autoplay hidden></audio>
{{ component(placed_items) }}
{% endmacro %}
Binary file added static/notification-1.mp3
Binary file not shown.

0 comments on commit 216dc48

Please sign in to comment.