Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions api/tests/test_info_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def test_info_v1_endpoint(

# Assert response data
expected_data = {
'viewlog': 'Not yet implemented',
'viewlog': 'No data',
'loadavg': 0.11,
'free_space': '15G',
'display_power': 'off',
Expand Down Expand Up @@ -128,7 +128,7 @@ def test_info_v2_endpoint(

# Assert response data
expected_data = {
'viewlog': 'Not yet implemented',
'viewlog': 'No data',
'loadavg': 0.25,
'free_space': '20G',
'display_power': 'on',
Expand Down
62 changes: 58 additions & 4 deletions api/views/mixins.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import logging
import sqlite3
import uuid
from base64 import b64encode
from inspect import cleandoc
from mimetypes import guess_extension, guess_type
from os import path, remove, statvfs
from os import getenv, path, remove, statvfs

from drf_spectacular.utils import OpenApiParameter, OpenApiTypes, extend_schema
from hurry.filesize import size
Expand Down Expand Up @@ -284,20 +286,72 @@ def get(self, request, command):


class InfoViewMixin(APIView):
@staticmethod
def _get_viewlog():
"""Read the last played asset from viewlog.db.

Returns a dict with the last entry or 'No data' if empty.
"""
home = getenv('HOME', '')
db_path = path.join(home, '.screenly', 'viewlog.db')

if not path.exists(db_path):
return 'No data'

try:
conn = sqlite3.connect(db_path, timeout=3)
conn.row_factory = sqlite3.Row
row = conn.execute(
'SELECT asset_id, asset_name, mimetype, started_at '
'FROM viewlog ORDER BY id DESC LIMIT 1'
).fetchone()
conn.close()
except Exception as e:
logging.warning('Failed to read viewlog: %s', e)
return 'No data'

if not row:
return 'No data'

return {
'asset_id': row['asset_id'],
'asset_name': row['asset_name'],
'mimetype': row['mimetype'],
'started_at': row['started_at'],
}

@extend_schema(
summary='Get system information',
responses={
200: {
'type': 'object',
'properties': {
'viewlog': {'type': 'string'},
'viewlog': {
'oneOf': [
{
'type': 'object',
'properties': {
'asset_id': {'type': 'string'},
'asset_name': {'type': 'string'},
'mimetype': {'type': 'string'},
'started_at': {'type': 'string'},
},
},
{'type': 'string'},
],
},
'loadavg': {'type': 'number'},
'free_space': {'type': 'string'},
'display_power': {'type': 'string'},
'up_to_date': {'type': 'boolean'},
},
'example': {
'viewlog': 'Not yet implemented',
'viewlog': {
'asset_id': 'abc123',
'asset_name': 'My Video',
'mimetype': 'video',
'started_at': '2024-01-01T12:00:00+00:00',
},
'loadavg': 0.1,
'free_space': '10G',
'display_power': 'on',
Expand All @@ -308,7 +362,7 @@ class InfoViewMixin(APIView):
)
@authorized
def get(self, request):
viewlog = 'Not yet implemented'
viewlog = self._get_viewlog()

# Calculate disk space
slash = statvfs('/')
Expand Down
17 changes: 15 additions & 2 deletions api/views/v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,20 @@ def get_ip_addresses(self):
200: {
'type': 'object',
'properties': {
'viewlog': {'type': 'string'},
'viewlog': {
'oneOf': [
{
'type': 'object',
'properties': {
'asset_id': {'type': 'string'},
'asset_name': {'type': 'string'},
'mimetype': {'type': 'string'},
'started_at': {'type': 'string'},
},
},
{'type': 'string'},
],
},
'loadavg': {'type': 'number'},
'free_space': {'type': 'string'},
'display_power': {'type': ['string', 'null']},
Expand Down Expand Up @@ -463,7 +476,7 @@ def get_ip_addresses(self):
)
@authorized
def get(self, request):
viewlog = 'Not yet implemented'
viewlog = self._get_viewlog()

# Calculate disk space
slash = statvfs('/')
Expand Down
60 changes: 60 additions & 0 deletions viewer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

import json
import logging
import sqlite3
import sys
from builtins import range
from datetime import datetime, timezone
from os import getenv, path
from signal import SIGALRM, signal
from time import sleep
Expand Down Expand Up @@ -222,6 +224,62 @@ def load_settings():
)


def _get_viewlog_db_path():
"""Return the path to the viewlog database."""
return path.join(HOME or getenv('HOME', ''), '.screenly', 'viewlog.db')


def _init_viewlog_db():
"""Create the viewlog database and table if they don't exist."""
db_path = _get_viewlog_db_path()
try:
conn = sqlite3.connect(db_path, timeout=5)
conn.execute("""
CREATE TABLE IF NOT EXISTS viewlog (
id INTEGER PRIMARY KEY AUTOINCREMENT,
asset_id TEXT NOT NULL,
asset_name TEXT NOT NULL,
mimetype TEXT NOT NULL,
uri TEXT NOT NULL,
started_at TEXT NOT NULL,
duration INTEGER DEFAULT 0
)
""")
conn.commit()
conn.close()
except Exception as e:
logging.warning('Failed to init viewlog DB: %s', e)


def _log_playback(asset):
"""Log an asset playback event to viewlog.db.

Records what asset started playing and when. This data is used by
the /api/v2/info endpoint and the screenshot endpoint to know
what is currently displayed.
"""
db_path = _get_viewlog_db_path()
try:
conn = sqlite3.connect(db_path, timeout=5)
conn.execute(
'INSERT INTO viewlog '
'(asset_id, asset_name, mimetype, uri, started_at, duration) '
'VALUES (?, ?, ?, ?, ?, ?)',
(
asset.get('asset_id', ''),
asset.get('name', ''),
asset.get('mimetype', ''),
asset.get('uri', ''),
datetime.now(timezone.utc).isoformat(),
int(asset.get('duration', 0)),
),
)
conn.commit()
conn.close()
except Exception as e:
logging.warning('Failed to log playback: %s', e)


def asset_loop(scheduler):
asset = scheduler.get_next_asset()

Expand All @@ -248,6 +306,7 @@ def asset_loop(scheduler):
logging.info('Showing asset %s (%s)', name, mime)
logging.debug('Asset URI %s', uri)
watchdog()
_log_playback(asset)

if 'image' in mime:
view_image(uri)
Expand Down Expand Up @@ -302,6 +361,7 @@ def setup():
signal(SIGALRM, sigalrm)

load_settings()
_init_viewlog_db()
load_browser()

bus = pydbus.SessionBus()
Expand Down
Loading