diff --git a/backend/src/app.py b/backend/src/app.py index 75a8ca7..3ce975b 100644 --- a/backend/src/app.py +++ b/backend/src/app.py @@ -1,7 +1,9 @@ import dotenv +from connect import get_connection from flask import Flask, Response, jsonify, request from flask_cors import CORS from flask_socketio import SocketIO, emit +from state import State from .room import Room from .song import Song, init_db, get_song_by_title_artist, add_song_in_db @@ -15,8 +17,9 @@ app.config["SECRET_KEY"] = "your_secret_key" socketio = SocketIO(app) CORS(app) - -ROOMS: dict[int, Room] = {} # { room_id: room, ... } +db_conn = get_connection() +state = State(app, db_conn.cursor()) +init_db(state.db) def error(msg: str, status: int = 400) -> Response: @@ -33,7 +36,7 @@ def join(): if room_id is None: return error("Missing room id") - if (room := ROOMS.get(int(room_id))) is None: + if (room := state.rooms.get(int(room_id))) is None: return error("Invalid room") if room.pin is not None and room.pin != code: @@ -47,7 +50,7 @@ def queue(): if (room_id := request.args.get("room")) is None: return error("Missing room id") - if (room := ROOMS.get(int(room_id))) is None: + if (room := state.rooms.get(int(room_id))) is None: return error("Invalid room") return {"success": True, "queue": room.playing} @@ -58,7 +61,7 @@ def queue_next(): if (room_id := request.args.get("room")) is None: return error("Missing room id") - if (room := ROOMS.get(int(room_id))) is None: + if (room := state.rooms.get(int(room_id))) is None: return error("Invalid room") room.playing_idx += 1 @@ -90,7 +93,7 @@ def room_new(): lat, lon = room_cords.split(",") room = Room( - id=max(ROOMS or [0]) + 1, # + id=max(state.rooms or [0]) + 1, # coord=(float(lat), float(lon)), name=room_name, pin=room_pin, @@ -101,7 +104,7 @@ def room_new(): playing_idx=-1, ) - ROOMS[room.id] = room + state.rooms[room.id] = room return {"success": True, "room_id": room.id} @@ -115,7 +118,7 @@ def room(): "private": room.pin is not None, "coords": room.coord, } - for room in ROOMS.values() + for room in state.rooms.values() ] @@ -124,7 +127,7 @@ def add_song(): if (room_id := request.args.get("room")) is None: return error("Missing room id") - if (room := ROOMS.get(int(room_id))) is None: + if (room := state.rooms.get(int(room_id))) is None: return error("Invalid room") if (query := request.args.get("query")) is None: @@ -135,7 +138,7 @@ def add_song(): if (song := get_song_by_title_artist(info.title, info.artist)) is None: res = download_song_mp3(info.title, info.artist) if res is None: - ops + return error("Cannot get info from YT") yt_id, _ = res ## song not found, downolad from YT ## add in DB diff --git a/backend/src/connect.py b/backend/src/connect.py index 7bc991f..c07af1b 100644 --- a/backend/src/connect.py +++ b/backend/src/connect.py @@ -4,4 +4,5 @@ import sqlite3 def get_connection(): conn = sqlite3.connect(".data/jukebox.db") conn.row_factory = sqlite3.Row + conn.autocommit = True return conn diff --git a/backend/src/room.py b/backend/src/room.py index 7fc051d..bee8b7f 100644 --- a/backend/src/room.py +++ b/backend/src/room.py @@ -9,6 +9,7 @@ TAG_WEIGHT = 0.1 RANDOM_WEIGHT = 0.1 RECENT_PENALTY = 0.5 RECENT_COUNT = 10 +QUEUE_SIZE = 3 type UserScoredSong = tuple[Song, int] @@ -34,11 +35,26 @@ class Room: pin: int | None tags: set[str] - songs: dict[str, UserScoredSong] # canzoni + voto - history: list[Song] # canzoni riprodotte (in ordine) - playing: list[Song] # canzoni che sono i riproduzione + songs: dict[str, UserScoredSong] # all songs + user score (the playlist) + history: list[Song] # all songs previously played + + playing: list[Song] # queue playing_idx: int + def renew_queue(self): + for song in self.playing: + self.history.append(song) + self.playing.clear() + self.playing_idx = 0 + + rankings: dict[str, float] = {} + for id, (song, user_score) in self.songs.items(): + rankings[id] = self.rank_song(song, user_score).total() + + # sort dict items by their values and pick the highest 3 + top_items = sorted(rankings.items(), key=lambda item: item[1], reverse=True)[:QUEUE_SIZE] # [:3] + self.playing = list(map(lambda x: self.songs[x[0]][0], top_items)) # remove the ranking and take only the songs + def rank_song_from_id(self, id: str) -> Rank: scored = self.songs[id] return self.rank_song(scored[0], scored[1]) @@ -109,18 +125,18 @@ def test_algo(): None, set(["rock", "rap"]), { - "paulham": (songs[0], 2), - "cisco": (songs[1], 1), - "vpn": (songs[2], 5), - "gang": (songs[3], 1), - "bertha1": (songs[4], 1), - "bertha2": (songs[5], 1), - "bertha3": (songs[6], 1), - "cexx": (songs[7], -123123), + "paulham": (songs[0], 7), + "cisco": (songs[1], 5), + "vpn": (songs[2], 11), + "gang": (songs[3], 10), + "bertha1": (songs[4], 4), + "bertha2": (songs[5], 5), + "bertha3": (songs[6], -4), + "cexx": (songs[7], 12), }, - [songs[4], songs[5], songs[0]], + [], + [songs[2], songs[0], songs[1]], + 1, ) - - print(room.rank_song_from_id("paulham"), room.rank_song_from_id("paulham").total()) - print(room.rank_song_from_id("vpn"), room.rank_song_from_id("vpn").total()) - print(room.rank_song_from_id("cexx"), room.rank_song_from_id("cexx").total()) + room.renew_queue() + print(room.playing) diff --git a/backend/src/song.py b/backend/src/song.py index 1d82b93..31cdec6 100644 --- a/backend/src/song.py +++ b/backend/src/song.py @@ -1,12 +1,12 @@ from dataclasses import dataclass import uuid +from sqlite3 import Cursor + from .connect import get_connection -def init_db(): - conn = get_connection() - cursor = conn.cursor() - cursor.execute(""" +def init_db(db: Cursor): + db.execute(""" CREATE TABLE IF NOT EXISTS songs ( mbid TEXT PRIMARY KEY, title TEXT NOT NULL, @@ -16,11 +16,9 @@ def init_db(): youtube_id TEXT NOT NULL ); """) - conn.commit() - conn.close() -@dataclass +@dataclass(frozen=True) class Song: mbid: str title: str @@ -44,6 +42,20 @@ def get_song_by_mbid(mbid: str) -> Song | None: return song +def get_song_by_title_artist(title: str, artist: str) -> Song | None: + conn = get_connection() + cursor = conn.cursor() + cursor.execute("SELECT * FROM songs WHERE title = ? AND artist = ?", (title, artist)) + row = cursor.fetchone() + conn.close() + + if row is None: + return None + + song = Song(mbid=row["mbid"], title=row["title"], artist=row["artist"], tags=row["tags"].split(","), image_id=row["lastfm_image_id"], youtube_id=row["youtube_id"]) + return song + + def add_song_in_db(title: str, artist: str, tags: list[str], image_id: str, yt_id: str) -> None: conn = get_connection() cursor = conn.cursor() diff --git a/backend/src/state.py b/backend/src/state.py new file mode 100644 index 0000000..7778117 --- /dev/null +++ b/backend/src/state.py @@ -0,0 +1,13 @@ +from dataclasses import dataclass +from sqlite3 import Cursor + +from flask import Flask + +from .room import Room + + +@dataclass +class State: + app: Flask + db: Cursor + rooms: dict[int, Room] = {} # { room_id: room, ... }