2025-02-21 14:11:39 -05:00
|
|
|
import logging
|
2025-01-21 19:16:23 -05:00
|
|
|
import time
|
2025-03-04 08:11:55 -05:00
|
|
|
import os
|
|
|
|
from typing import Optional, Annotated
|
2025-04-17 07:28:05 -04:00
|
|
|
from fastapi import FastAPI, Request, UploadFile, Response, HTTPException, Form
|
2025-02-15 21:09:33 -05:00
|
|
|
from fastapi.responses import JSONResponse
|
2025-01-21 19:16:23 -05:00
|
|
|
import redis.asyncio as redis
|
2025-01-24 09:10:54 -05:00
|
|
|
from lyric_search.sources import private, cache as LyricsCache, redis_cache
|
2025-01-21 19:16:23 -05:00
|
|
|
|
2025-04-17 07:28:05 -04:00
|
|
|
|
2025-01-21 19:16:23 -05:00
|
|
|
class Misc(FastAPI):
|
2025-02-16 08:17:27 -05:00
|
|
|
"""
|
|
|
|
Misc Endpoints
|
|
|
|
"""
|
2025-04-17 07:28:05 -04:00
|
|
|
|
|
|
|
def __init__(self, app: FastAPI, my_util, constants, radio) -> None:
|
2025-02-15 21:09:33 -05:00
|
|
|
self.app: FastAPI = app
|
2025-01-21 19:16:23 -05:00
|
|
|
self.util = my_util
|
|
|
|
self.constants = constants
|
|
|
|
self.lyr_cache = LyricsCache.Cache()
|
2025-01-22 06:38:40 -05:00
|
|
|
self.redis_cache = redis_cache.RedisCache()
|
2025-01-21 19:16:23 -05:00
|
|
|
self.redis_client = redis.Redis(password=private.REDIS_PW)
|
2025-02-11 08:41:29 -05:00
|
|
|
self.radio = radio
|
2025-03-04 08:11:55 -05:00
|
|
|
self.activity_image: Optional[bytes] = None
|
2025-02-11 20:01:07 -05:00
|
|
|
self.endpoints: dict = {
|
2025-01-21 19:16:23 -05:00
|
|
|
"widget/redis": self.homepage_redis_widget,
|
|
|
|
"widget/sqlite": self.homepage_sqlite_widget,
|
2025-01-22 06:38:40 -05:00
|
|
|
"widget/lyrics": self.homepage_lyrics_widget,
|
2025-04-17 07:28:05 -04:00
|
|
|
"widget/radio": self.homepage_radio_widget,
|
2025-03-04 08:11:55 -05:00
|
|
|
"misc/get_activity_image": self.get_activity_image,
|
2025-01-21 19:16:23 -05:00
|
|
|
}
|
2025-04-17 07:28:05 -04:00
|
|
|
|
2025-01-21 19:16:23 -05:00
|
|
|
for endpoint, handler in self.endpoints.items():
|
2025-04-17 07:28:05 -04:00
|
|
|
app.add_api_route(
|
|
|
|
f"/{endpoint}", handler, methods=["GET"], include_in_schema=True
|
|
|
|
)
|
|
|
|
|
|
|
|
app.add_api_route(
|
|
|
|
"/misc/upload_activity_image", self.upload_activity_image, methods=["POST"]
|
|
|
|
)
|
|
|
|
|
|
|
|
async def upload_activity_image(
|
|
|
|
self, image: UploadFile, key: Annotated[str, Form()], request: Request
|
|
|
|
) -> Response:
|
|
|
|
if (
|
|
|
|
not key
|
|
|
|
or not isinstance(key, str)
|
|
|
|
or not self.util.check_key(path=request.url.path, req_type=2, key=key)
|
|
|
|
):
|
|
|
|
raise HTTPException(status_code=403, detail="Unauthorized")
|
2025-03-04 08:11:55 -05:00
|
|
|
if not image:
|
2025-04-17 07:28:05 -04:00
|
|
|
return JSONResponse(
|
|
|
|
status_code=500,
|
|
|
|
content={
|
|
|
|
"err": True,
|
|
|
|
"errorText": "Invalid request",
|
|
|
|
},
|
|
|
|
)
|
2025-03-04 08:11:55 -05:00
|
|
|
self.activity_image = await image.read()
|
2025-04-17 07:28:05 -04:00
|
|
|
return JSONResponse(
|
|
|
|
content={
|
|
|
|
"success": True,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2025-03-04 08:11:55 -05:00
|
|
|
async def get_activity_image(self, request: Request) -> Response:
|
|
|
|
if isinstance(self.activity_image, bytes):
|
2025-04-17 07:28:05 -04:00
|
|
|
return Response(content=self.activity_image, media_type="image/png")
|
|
|
|
|
2025-03-04 08:11:55 -05:00
|
|
|
# Fallback
|
2025-04-17 07:28:05 -04:00
|
|
|
fallback_path = os.path.join(
|
|
|
|
"/var/www/codey.lol/public", "images", "plex_placeholder.png"
|
|
|
|
)
|
|
|
|
|
|
|
|
with open(fallback_path, "rb") as f:
|
|
|
|
return Response(content=f.read(), media_type="image/png")
|
|
|
|
|
2025-03-18 09:39:20 -04:00
|
|
|
async def get_radio_np(self) -> tuple[str, str, str]:
|
2025-01-22 09:00:51 -05:00
|
|
|
"""
|
|
|
|
Get radio now playing
|
|
|
|
Args:
|
|
|
|
None
|
|
|
|
Returns:
|
|
|
|
str: Radio now playing in artist - song format
|
|
|
|
"""
|
2025-04-17 07:28:05 -04:00
|
|
|
|
2025-03-18 09:39:20 -04:00
|
|
|
np: dict = self.radio.radio_util.now_playing
|
2025-04-17 07:28:05 -04:00
|
|
|
artistsong: str = np.get("artistsong", "N/A - N/A")
|
|
|
|
album: str = np.get("album", "N/A")
|
|
|
|
genre: str = np.get("genre", "N/A")
|
2025-03-18 09:39:20 -04:00
|
|
|
return (artistsong, album, genre)
|
2025-04-17 07:28:05 -04:00
|
|
|
|
2025-02-15 21:09:33 -05:00
|
|
|
async def homepage_redis_widget(self) -> JSONResponse:
|
2025-02-16 08:17:27 -05:00
|
|
|
"""
|
|
|
|
Homepage Redis Widget Handler
|
|
|
|
"""
|
2025-01-21 19:16:23 -05:00
|
|
|
# Measure response time w/ test lyric search
|
2025-04-17 07:28:05 -04:00
|
|
|
time_start: float = time.time() # Start time for response_time
|
|
|
|
test_lyrics_result = await self.redis_client.ft().search(
|
|
|
|
"@artist: test @song: test"
|
|
|
|
)
|
2025-01-21 19:16:23 -05:00
|
|
|
time_end: float = time.time()
|
|
|
|
# End response time test
|
|
|
|
total_keys = await self.redis_client.dbsize()
|
2025-04-17 07:28:05 -04:00
|
|
|
response_time: float = time_end - time_start
|
|
|
|
(_, ci_keys) = await self.redis_client.scan(
|
|
|
|
cursor=0, match="ci_session*", count=10000000
|
|
|
|
)
|
2025-01-21 19:16:23 -05:00
|
|
|
num_ci_keys = len(ci_keys)
|
|
|
|
index_info = await self.redis_client.ft().info()
|
2025-04-17 07:28:05 -04:00
|
|
|
indexed_lyrics: int = index_info.get("num_docs")
|
|
|
|
return JSONResponse(
|
|
|
|
content={
|
|
|
|
"responseTime": round(response_time, 7),
|
|
|
|
"storedKeys": total_keys,
|
|
|
|
"indexedLyrics": indexed_lyrics,
|
|
|
|
"sessions": num_ci_keys,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2025-02-15 21:09:33 -05:00
|
|
|
async def homepage_sqlite_widget(self) -> JSONResponse:
|
2025-02-16 08:17:27 -05:00
|
|
|
"""
|
|
|
|
Homepage SQLite Widget Handler
|
2025-04-17 07:28:05 -04:00
|
|
|
"""
|
2025-02-11 20:01:07 -05:00
|
|
|
row_count: int = await self.lyr_cache.sqlite_rowcount()
|
|
|
|
distinct_artists: int = await self.lyr_cache.sqlite_distinct("artist")
|
|
|
|
lyrics_length: int = await self.lyr_cache.sqlite_lyrics_length()
|
2025-04-17 07:28:05 -04:00
|
|
|
return JSONResponse(
|
|
|
|
content={
|
|
|
|
"storedRows": row_count,
|
|
|
|
"distinctArtists": distinct_artists,
|
|
|
|
"lyricsLength": lyrics_length,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2025-02-16 08:45:40 -05:00
|
|
|
async def homepage_lyrics_widget(self) -> JSONResponse:
|
2025-02-16 08:17:27 -05:00
|
|
|
"""
|
|
|
|
Homepage Lyrics Widget Handler
|
|
|
|
"""
|
2025-02-18 14:37:37 -05:00
|
|
|
found_counts: Optional[dict] = await self.redis_cache.get_found_counts()
|
2025-02-15 21:09:33 -05:00
|
|
|
if not isinstance(found_counts, dict):
|
2025-04-17 07:28:05 -04:00
|
|
|
logging.info(
|
|
|
|
"DEBUG: Type of found counts from redis: %s\nContents: %s",
|
|
|
|
type(found_counts),
|
|
|
|
found_counts,
|
|
|
|
)
|
|
|
|
return JSONResponse(
|
|
|
|
status_code=500,
|
|
|
|
content={
|
|
|
|
"err": True,
|
|
|
|
"errorText": "General failure.",
|
|
|
|
},
|
|
|
|
)
|
2025-02-16 08:45:40 -05:00
|
|
|
return JSONResponse(content=found_counts)
|
2025-04-17 07:28:05 -04:00
|
|
|
|
2025-02-15 21:09:33 -05:00
|
|
|
async def homepage_radio_widget(self) -> JSONResponse:
|
2025-02-16 08:17:27 -05:00
|
|
|
"""
|
|
|
|
Homepage Radio Widget Handler
|
|
|
|
"""
|
2025-03-18 09:39:20 -04:00
|
|
|
radio_np: tuple = await self.get_radio_np()
|
2025-02-15 21:09:33 -05:00
|
|
|
if not radio_np:
|
2025-04-17 07:28:05 -04:00
|
|
|
return JSONResponse(
|
|
|
|
status_code=500,
|
|
|
|
content={
|
|
|
|
"err": True,
|
|
|
|
"errorText": "General failure.",
|
|
|
|
},
|
|
|
|
)
|
2025-03-18 09:39:20 -04:00
|
|
|
(artistsong, album, genre) = radio_np
|
2025-04-17 07:28:05 -04:00
|
|
|
return JSONResponse(
|
|
|
|
content={
|
|
|
|
"now_playing": artistsong,
|
|
|
|
"album": album,
|
|
|
|
"genre": genre,
|
|
|
|
}
|
|
|
|
)
|